From 2483528d26a355b08f59f71a7f84473e8965bea2 Mon Sep 17 00:00:00 2001 From: Ilona Tomkowicz <32700855+ilonatommy@users.noreply.github.com> Date: Tue, 19 Mar 2024 13:02:27 +0000 Subject: [PATCH 01/18] Fix NativeName and DisplayName for browser. --- .../src/Interop/Browser/Interop.Locale.cs | 2 + .../Globalization/CultureData.Browser.cs | 16 ++++++++ .../System/Globalization/CultureData.Icu.cs | 15 ++++++- .../CultureInfo/CultureInfoNames.cs | 34 +++++++++++----- src/mono/browser/runtime/corebindings.c | 2 + src/mono/browser/runtime/exports-binding.ts | 3 +- .../runtime/hybrid-globalization/locales.ts | 40 ++++++++++++++++++- 7 files changed, 97 insertions(+), 15 deletions(-) diff --git a/src/libraries/Common/src/Interop/Browser/Interop.Locale.cs b/src/libraries/Common/src/Interop/Browser/Interop.Locale.cs index c882d88afac25..e86d87521be5e 100644 --- a/src/libraries/Common/src/Interop/Browser/Interop.Locale.cs +++ b/src/libraries/Common/src/Interop/Browser/Interop.Locale.cs @@ -13,5 +13,7 @@ internal static unsafe partial class JsGlobalization internal static extern unsafe int GetFirstDayOfWeek(in string culture, out int exceptionalResult, out object result); [MethodImplAttribute(MethodImplOptions.InternalCall)] internal static extern unsafe int GetFirstWeekOfYear(in string culture, out int exceptionalResult, out object result); + [MethodImplAttribute(MethodImplOptions.InternalCall)] + internal static extern unsafe int GetNativeName(in string locale, in string culture, char* buffer, int bufferLength, out int exceptionalResult, out object result); } } diff --git a/src/libraries/System.Private.CoreLib/src/System/Globalization/CultureData.Browser.cs b/src/libraries/System.Private.CoreLib/src/System/Globalization/CultureData.Browser.cs index fcc5943e7b766..1ccd4c4e6701e 100644 --- a/src/libraries/System.Private.CoreLib/src/System/Globalization/CultureData.Browser.cs +++ b/src/libraries/System.Private.CoreLib/src/System/Globalization/CultureData.Browser.cs @@ -11,6 +11,22 @@ internal sealed partial class CultureData { private const int CULTURE_INFO_BUFFER_LEN = 50; + private unsafe string JSGetNativeName(string localeName, string? uiCultureName = null) + { + if (string.IsNullOrEmpty(localeName)) + return "Invariant Language (Invariant Country)"; + + string cultureName = uiCultureName ?? localeName; + // the longest possible NativeName is 50 characters + char* buffer = stackalloc char[CULTURE_INFO_BUFFER_LEN]; + int exception; + object exResult; + int resultLength = Interop.JsGlobalization.GetNativeName(localeName, cultureName, buffer, CULTURE_INFO_BUFFER_LEN, out exception, out exResult); + if (exception != 0) + throw new Exception((string)exResult); + return new string(buffer, 0, resultLength); + } + private static unsafe CultureData JSLoadCultureInfoFromBrowser(string localeName, CultureData culture) { char* buffer = stackalloc char[CULTURE_INFO_BUFFER_LEN]; 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 435e7ba9d45fd..fc34abb4b7bce 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 @@ -203,6 +203,12 @@ private string IcuGetLocaleInfo(LocaleStringData type, string? uiCultureName = n Debug.Assert(!GlobalizationMode.Invariant); Debug.Assert(!GlobalizationMode.UseNls); Debug.Assert(_sWindowsName != null, "[CultureData.IcuGetLocaleInfo] Expected _sWindowsName to be populated already"); +#if TARGET_BROWSER + if (type == LocaleStringData.NativeDisplayName) + { + return JSGetNativeName(_sWindowsName, uiCultureName); + } +#endif return IcuGetLocaleInfo(_sWindowsName, type, uiCultureName); } @@ -302,7 +308,14 @@ private unsafe string IcuGetTimeFormatString(bool shortFormat) // no support to lookup by region name, other than the hard-coded list in CultureData private static CultureData? IcuGetCultureDataFromRegionName() => null; - private string IcuGetLanguageDisplayName(string cultureName) => IcuGetLocaleInfo(cultureName, LocaleStringData.LocalizedDisplayName, CultureInfo.CurrentUICulture.Name); + private string IcuGetLanguageDisplayName(string cultureName) + { +#if TARGET_BROWSER + return JSGetNativeName(CultureInfo.CurrentUICulture.Name, cultureName); +#else + return IcuGetLocaleInfo(cultureName, LocaleStringData.LocalizedDisplayName, CultureInfo.CurrentUICulture.Name); +#endif + } // use the fallback which is to return NativeName private static string? IcuGetRegionDisplayName() => null; diff --git a/src/libraries/System.Runtime/tests/System.Globalization.Tests/CultureInfo/CultureInfoNames.cs b/src/libraries/System.Runtime/tests/System.Globalization.Tests/CultureInfo/CultureInfoNames.cs index d2c0828bc6850..fdde2c84ab56b 100644 --- a/src/libraries/System.Runtime/tests/System.Globalization.Tests/CultureInfo/CultureInfoNames.cs +++ b/src/libraries/System.Runtime/tests/System.Globalization.Tests/CultureInfo/CultureInfoNames.cs @@ -10,19 +10,31 @@ namespace System.Globalization.Tests { public class CultureInfoNames { - private static bool SupportFullIcuResources => (PlatformDetection.IsNotMobile && PlatformDetection.IsIcuGlobalization) || PlatformDetection.IsHybridGlobalizationOnApplePlatform; + private static bool SupportFullIcuResources => + (PlatformDetection.IsNotMobile && PlatformDetection.IsIcuGlobalization) || + PlatformDetection.IsHybridGlobalizationOnApplePlatform || + PlatformDetection.IsBrowser; + + public static IEnumerable SupportedCultures_TestData() + { + // Browser does not support all ICU locales but it uses JS to get the correct native name + if (!PlatformDetection.IsBrowser) + { + yield return new object[] { "aa", "aa", "Afar", "Afar" }; + yield return new object[] { "aa-ER", "aa-ER", "Afar (Eritrea)", "Afar (Eritrea)" }; + } + yield return new object[] { "en", "en", "English", "English" }; + yield return new object[] { "en", "fr", "English", "anglais" }; + yield return new object[] { "en-US", "en-US", "English (United States)", "English (United States)" }; + yield return new object[] { "en-US", "fr-FR", "English (United States)", "anglais (\u00C9tats-Unis)" }; + yield return new object[] { "en-US", "de-DE", "English (United States)", "Englisch (Vereinigte Staaten)" }; + yield return new object[] { "", "en-US", "Invariant Language (Invariant Country)", "Invariant Language (Invariant Country)" }; + yield return new object[] { "", "fr-FR", "Invariant Language (Invariant Country)", "Invariant Language (Invariant Country)" }; + yield return new object[] { "", "", "Invariant Language (Invariant Country)", "Invariant Language (Invariant Country)" }; + } [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 (\u00C9tats-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)")] + [MemberData(nameof(SupportedCultures_TestData))] public void TestDisplayName(string cultureName, string uiCultureName, string nativeName, string displayName) { using (new ThreadCultureChange(null, CultureInfo.GetCultureInfo(uiCultureName))) diff --git a/src/mono/browser/runtime/corebindings.c b/src/mono/browser/runtime/corebindings.c index 1b0c0a809f63e..d37da93b57b4d 100644 --- a/src/mono/browser/runtime/corebindings.c +++ b/src/mono/browser/runtime/corebindings.c @@ -64,6 +64,7 @@ extern mono_bool mono_wasm_starts_with (MonoString **culture, const uint16_t* st extern mono_bool mono_wasm_ends_with (MonoString **culture, const uint16_t* str1, int32_t str1Length, const uint16_t* str2, int32_t str2Length, int32_t options, int *is_exception, MonoObject** ex_result); extern int mono_wasm_index_of (MonoString **culture, const uint16_t* str1, int32_t str1Length, const uint16_t* str2, int32_t str2Length, int32_t options, mono_bool fromBeginning, int *is_exception, MonoObject** ex_result); extern int mono_wasm_get_calendar_info (MonoString **culture, int32_t calendarId, const uint16_t* result, int32_t resultLength, int *is_exception, MonoObject** ex_result); +extern int mono_wasm_get_native_display_name (MonoString **locale, MonoString **culture, const uint16_t* result, int32_t resultLength, int *is_exception, MonoObject** ex_result); extern int mono_wasm_get_culture_info (MonoString **culture, const uint16_t* result, int32_t resultLength, int *is_exception, MonoObject** ex_result); extern int mono_wasm_get_first_day_of_week (MonoString **culture, int *is_exception, MonoObject** ex_result); extern int mono_wasm_get_first_week_of_year (MonoString **culture, int *is_exception, MonoObject** ex_result); @@ -105,6 +106,7 @@ void bindings_initialize_internals (void) mono_add_internal_call ("Interop/JsGlobalization::EndsWith", mono_wasm_ends_with); mono_add_internal_call ("Interop/JsGlobalization::IndexOf", mono_wasm_index_of); mono_add_internal_call ("Interop/JsGlobalization::GetCalendarInfo", mono_wasm_get_calendar_info); + mono_add_internal_call ("Interop/JsGlobalization::GetNativeName", mono_wasm_get_native_display_name); mono_add_internal_call ("Interop/JsGlobalization::GetCultureInfo", mono_wasm_get_culture_info); mono_add_internal_call ("Interop/JsGlobalization::GetFirstDayOfWeek", mono_wasm_get_first_day_of_week); mono_add_internal_call ("Interop/JsGlobalization::GetFirstWeekOfYear", mono_wasm_get_first_week_of_year); diff --git a/src/mono/browser/runtime/exports-binding.ts b/src/mono/browser/runtime/exports-binding.ts index d2ff55bc088a7..cbd07f066217b 100644 --- a/src/mono/browser/runtime/exports-binding.ts +++ b/src/mono/browser/runtime/exports-binding.ts @@ -22,7 +22,7 @@ import { mono_wasm_compare_string, mono_wasm_ends_with, mono_wasm_starts_with, m import { mono_wasm_get_calendar_info } from "./hybrid-globalization/calendar"; import { mono_wasm_get_culture_info } from "./hybrid-globalization/culture-info"; -import { mono_wasm_get_first_day_of_week, mono_wasm_get_first_week_of_year } from "./hybrid-globalization/locales"; +import { mono_wasm_get_native_display_name, mono_wasm_get_first_day_of_week, mono_wasm_get_first_week_of_year } from "./hybrid-globalization/locales"; import { mono_wasm_browser_entropy } from "./crypto"; import { mono_wasm_cancel_promise } from "./cancelable-promise"; @@ -107,6 +107,7 @@ export const mono_wasm_imports = [ mono_wasm_index_of, mono_wasm_get_calendar_info, mono_wasm_get_culture_info, + mono_wasm_get_native_display_name, mono_wasm_get_first_day_of_week, mono_wasm_get_first_week_of_year, ]; diff --git a/src/mono/browser/runtime/hybrid-globalization/locales.ts b/src/mono/browser/runtime/hybrid-globalization/locales.ts index 4330d0977e360..add695eacf5ca 100644 --- a/src/mono/browser/runtime/hybrid-globalization/locales.ts +++ b/src/mono/browser/runtime/hybrid-globalization/locales.ts @@ -3,11 +3,47 @@ import { wrap_error_root, wrap_no_error_root } from "../invoke-js"; import { mono_wasm_new_external_root } from "../roots"; -import { monoStringToString } from "../strings"; +import { monoStringToString, stringToUTF16 } from "../strings"; import { Int32Ptr } from "../types/emscripten"; import { MonoObject, MonoObjectRef, MonoString, MonoStringRef } from "../types/internal"; import { normalizeLocale } from "./helpers"; +export function mono_wasm_get_native_display_name(locale: MonoStringRef, culture: MonoStringRef, dst: number, dstLength: number, isException: Int32Ptr, exAddress: MonoObjectRef) : number +{ + const localeRoot = mono_wasm_new_external_root(locale), + cultureRoot = mono_wasm_new_external_root(culture), + exceptionRoot = mono_wasm_new_external_root(exAddress); + try { + const localeName = monoStringToString(localeRoot); + const cultureName = monoStringToString(cultureRoot); + if (!localeName || !cultureName) + throw new Error("Locale or culture name is null or empty."); + + const [language, region] = cultureName.split("-"); + const languageName = new Intl.DisplayNames([localeName], {type: "language"}).of(language); + const regionName = region ? new Intl.DisplayNames([localeName], {type: "region"}).of(region) : undefined; + const result = region ? `${languageName} (${regionName})` : languageName; + + if (!result) + throw new Error(`Native display name for culture=${cultureName} is null or empty.`); + + if (result.length > dstLength) + throw new Error(`Native display name for culture=${cultureName} exceeds length of ${dstLength}.`); + + stringToUTF16(dst, dst + 2 * result.length, result); + wrap_no_error_root(isException, exceptionRoot); + return result.length; + } + catch (ex: any) { + wrap_error_root(isException, ex, exceptionRoot); + return -1; + } + finally { + cultureRoot.release(); + exceptionRoot.release(); + } +} + export function mono_wasm_get_first_day_of_week(culture: MonoStringRef, isException: Int32Ptr, exAddress: MonoObjectRef): number{ const cultureRoot = mono_wasm_new_external_root(culture), @@ -53,7 +89,7 @@ function getFirstDayOfWeek(locale: string) const weekInfo = getWeekInfo(locale); if (weekInfo) { - // JS's Sunday == 7 while dotnet's Sunday == 0 + // JS"s Sunday == 7 while dotnet"s Sunday == 0 return weekInfo.firstDay == 7 ? 0 : weekInfo.firstDay; } // Firefox does not support it rn but we can make a temporary workaround for it, From e4f4ffcc0e5981ee762f0843559aa987a9efd897 Mon Sep 17 00:00:00 2001 From: Ilona Tomkowicz <32700855+ilonatommy@users.noreply.github.com> Date: Tue, 19 Mar 2024 13:18:57 +0000 Subject: [PATCH 02/18] Nit - rever over-renaming. --- src/mono/browser/runtime/hybrid-globalization/locales.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/mono/browser/runtime/hybrid-globalization/locales.ts b/src/mono/browser/runtime/hybrid-globalization/locales.ts index add695eacf5ca..0841a8e02f5bf 100644 --- a/src/mono/browser/runtime/hybrid-globalization/locales.ts +++ b/src/mono/browser/runtime/hybrid-globalization/locales.ts @@ -89,7 +89,7 @@ function getFirstDayOfWeek(locale: string) const weekInfo = getWeekInfo(locale); if (weekInfo) { - // JS"s Sunday == 7 while dotnet"s Sunday == 0 + // JS's Sunday == 7 while dotnet's Sunday == 0 return weekInfo.firstDay == 7 ? 0 : weekInfo.firstDay; } // Firefox does not support it rn but we can make a temporary workaround for it, From e438304e853898d5b20c89a64fd28b55a4e9bd3d Mon Sep 17 00:00:00 2001 From: Ilona Tomkowicz <32700855+ilonatommy@users.noreply.github.com> Date: Tue, 19 Mar 2024 18:51:21 +0100 Subject: [PATCH 03/18] Update src/libraries/System.Private.CoreLib/src/System/Globalization/CultureData.Browser.cs Co-authored-by: Meri Khamoyan <96171496+mkhamoyan@users.noreply.github.com> --- .../src/System/Globalization/CultureData.Browser.cs | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/src/libraries/System.Private.CoreLib/src/System/Globalization/CultureData.Browser.cs b/src/libraries/System.Private.CoreLib/src/System/Globalization/CultureData.Browser.cs index 1ccd4c4e6701e..0fea0dcae9583 100644 --- a/src/libraries/System.Private.CoreLib/src/System/Globalization/CultureData.Browser.cs +++ b/src/libraries/System.Private.CoreLib/src/System/Globalization/CultureData.Browser.cs @@ -19,9 +19,7 @@ private unsafe string JSGetNativeName(string localeName, string? uiCultureName = string cultureName = uiCultureName ?? localeName; // the longest possible NativeName is 50 characters char* buffer = stackalloc char[CULTURE_INFO_BUFFER_LEN]; - int exception; - object exResult; - int resultLength = Interop.JsGlobalization.GetNativeName(localeName, cultureName, buffer, CULTURE_INFO_BUFFER_LEN, out exception, out exResult); + int resultLength = Interop.JsGlobalization.GetNativeName(localeName, cultureName, buffer, CULTURE_INFO_BUFFER_LEN, out int exception, out object exResult); if (exception != 0) throw new Exception((string)exResult); return new string(buffer, 0, resultLength); From 5dee365448f006414623783d235f0356b3d46e41 Mon Sep 17 00:00:00 2001 From: Ilona Tomkowicz <32700855+ilonatommy@users.noreply.github.com> Date: Wed, 20 Mar 2024 17:55:40 +0000 Subject: [PATCH 04/18] Enable more tests and fix them - initialize english and native names on CultureInfo constructio --- .../src/Interop/Browser/Interop.Locale.cs | 2 +- .../Globalization/CultureData.Browser.cs | 56 +++++++++++--- .../System/Globalization/CultureData.Icu.cs | 4 +- .../src/System/Globalization/CultureData.cs | 1 + .../CultureInfo/CultureInfoEnglishName.cs | 13 ++-- .../CultureInfo/CultureInfoNativeName.cs | 5 +- .../System/Globalization/RegionInfoTests.cs | 14 ++-- src/mono/browser/runtime/corebindings.c | 4 +- src/mono/browser/runtime/exports-binding.ts | 4 +- .../runtime/hybrid-globalization/helpers.ts | 4 +- .../runtime/hybrid-globalization/locales.ts | 74 ++++++++++++++++--- 11 files changed, 138 insertions(+), 43 deletions(-) diff --git a/src/libraries/Common/src/Interop/Browser/Interop.Locale.cs b/src/libraries/Common/src/Interop/Browser/Interop.Locale.cs index e86d87521be5e..b831e72e70cdb 100644 --- a/src/libraries/Common/src/Interop/Browser/Interop.Locale.cs +++ b/src/libraries/Common/src/Interop/Browser/Interop.Locale.cs @@ -14,6 +14,6 @@ internal static unsafe partial class JsGlobalization [MethodImplAttribute(MethodImplOptions.InternalCall)] internal static extern unsafe int GetFirstWeekOfYear(in string culture, out int exceptionalResult, out object result); [MethodImplAttribute(MethodImplOptions.InternalCall)] - internal static extern unsafe int GetNativeName(in string locale, in string culture, char* buffer, int bufferLength, out int exceptionalResult, out object result); + internal static extern unsafe int GetLocaleInfo(in string locale, in string culture, char* buffer, int bufferLength, out int exceptionalResult, out object result); } } diff --git a/src/libraries/System.Private.CoreLib/src/System/Globalization/CultureData.Browser.cs b/src/libraries/System.Private.CoreLib/src/System/Globalization/CultureData.Browser.cs index 0fea0dcae9583..e8f2927fb4d72 100644 --- a/src/libraries/System.Private.CoreLib/src/System/Globalization/CultureData.Browser.cs +++ b/src/libraries/System.Private.CoreLib/src/System/Globalization/CultureData.Browser.cs @@ -10,27 +10,63 @@ namespace System.Globalization internal sealed partial class CultureData { private const int CULTURE_INFO_BUFFER_LEN = 50; + private const int LOCALE_INFO_BUFFER_LEN = 80; - private unsafe string JSGetNativeName(string localeName, string? uiCultureName = null) + private static CultureData JSInitLocaleInfo(string? localeName, CultureData culture) { if (string.IsNullOrEmpty(localeName)) - return "Invariant Language (Invariant Country)"; + { + culture._sEnglishLanguage = "Invariant Language"; + culture._sNativeLanguage = culture._sEnglishLanguage; + culture._sEnglishCountry = "Invariant Country"; + culture._sNativeCountry = culture._sEnglishCountry; + culture._sEnglishDisplayName = $"{culture._sEnglishLanguage} ({culture._sEnglishCountry})"; + culture._sNativeDisplayName = culture._sEnglishDisplayName; + } + else + { + // ToDo: make sure we do not re-ask for it + // English locale info + (culture._sEnglishLanguage, culture._sEnglishCountry) = culture.JSGetLocaleInfo("en-US", localeName); + culture._sEnglishDisplayName = string.IsNullOrEmpty(culture._sEnglishCountry) ? + culture._sEnglishLanguage : + $"{culture._sEnglishLanguage} ({culture._sEnglishCountry})"; + // Native locale info + (culture._sNativeLanguage, culture._sNativeCountry) = culture.JSGetLocaleInfo(localeName, localeName); + culture._sNativeDisplayName = string.IsNullOrEmpty(culture._sNativeCountry) ? + culture._sNativeLanguage : + $"{culture._sNativeLanguage} ({culture._sNativeCountry})"; + } + return culture; + } - string cultureName = uiCultureName ?? localeName; - // the longest possible NativeName is 50 characters - char* buffer = stackalloc char[CULTURE_INFO_BUFFER_LEN]; - int resultLength = Interop.JsGlobalization.GetNativeName(localeName, cultureName, buffer, CULTURE_INFO_BUFFER_LEN, out int exception, out object exResult); + private unsafe (string, string) JSGetLocaleInfo(string cultureName, string localeName) + { + char* buffer = stackalloc char[LOCALE_INFO_BUFFER_LEN]; + int resultLength = Interop.JsGlobalization.GetLocaleInfo(cultureName, localeName, buffer, LOCALE_INFO_BUFFER_LEN, out int exception, out object exResult); if (exception != 0) throw new Exception((string)exResult); - return new string(buffer, 0, resultLength); + string result = new string(buffer, 0, resultLength); + string[] subresults = result.Split("##"); + if (subresults.Length == 0) + throw new Exception("LocaleInfo recieved from the Browser is in incorrect format."); + if (subresults.Length == 1) + return (subresults[0], ""); // Neutral culture + return (subresults[0], subresults[1]); + } + + private string JSGetNativeDisplayName(string localeName, string cultureName) + { + (string languageName, string countryName) = JSGetLocaleInfo(localeName, cultureName); + return string.IsNullOrEmpty(countryName) ? + languageName : + $"{languageName} ({countryName})"; } private static unsafe CultureData JSLoadCultureInfoFromBrowser(string localeName, CultureData culture) { char* buffer = stackalloc char[CULTURE_INFO_BUFFER_LEN]; - int exception; - object exResult; - int resultLength = Interop.JsGlobalization.GetCultureInfo(localeName, buffer, CULTURE_INFO_BUFFER_LEN, out exception, out exResult); + int resultLength = Interop.JsGlobalization.GetCultureInfo(localeName, buffer, CULTURE_INFO_BUFFER_LEN, out int exception, out object exResult); if (exception != 0) throw new Exception((string)exResult); string result = new string(buffer, 0, resultLength); 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 fc34abb4b7bce..32d26e35433fb 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 @@ -206,7 +206,7 @@ private string IcuGetLocaleInfo(LocaleStringData type, string? uiCultureName = n #if TARGET_BROWSER if (type == LocaleStringData.NativeDisplayName) { - return JSGetNativeName(_sWindowsName, uiCultureName); + return JSGetNativeDisplayName(_sWindowsName, uiCultureName ?? _sWindowsName); } #endif return IcuGetLocaleInfo(_sWindowsName, type, uiCultureName); @@ -311,7 +311,7 @@ private unsafe string IcuGetTimeFormatString(bool shortFormat) private string IcuGetLanguageDisplayName(string cultureName) { #if TARGET_BROWSER - return JSGetNativeName(CultureInfo.CurrentUICulture.Name, cultureName); + return JSGetNativeDisplayName(CultureInfo.CurrentUICulture.Name, cultureName); #else return IcuGetLocaleInfo(cultureName, LocaleStringData.LocalizedDisplayName, CultureInfo.CurrentUICulture.Name); #endif diff --git a/src/libraries/System.Private.CoreLib/src/System/Globalization/CultureData.cs b/src/libraries/System.Private.CoreLib/src/System/Globalization/CultureData.cs index 7013429ad4c0e..7a543a9ecacdd 100644 --- a/src/libraries/System.Private.CoreLib/src/System/Globalization/CultureData.cs +++ b/src/libraries/System.Private.CoreLib/src/System/Globalization/CultureData.cs @@ -822,6 +822,7 @@ private static string NormalizeCultureName(string name, out bool isNeutralName) { culture = JSLoadCultureInfoFromBrowser(culture._sName, culture); } + culture = JSInitLocaleInfo(culture._sName, culture); #endif // We need _sWindowsName to be initialized to know if we're using overrides. diff --git a/src/libraries/System.Runtime/tests/System.Globalization.Tests/CultureInfo/CultureInfoEnglishName.cs b/src/libraries/System.Runtime/tests/System.Globalization.Tests/CultureInfo/CultureInfoEnglishName.cs index 02ee539981adb..5d040857b5fd0 100644 --- a/src/libraries/System.Runtime/tests/System.Globalization.Tests/CultureInfo/CultureInfoEnglishName.cs +++ b/src/libraries/System.Runtime/tests/System.Globalization.Tests/CultureInfo/CultureInfoEnglishName.cs @@ -9,13 +9,14 @@ namespace System.Globalization.Tests public class CultureInfoEnglishName { // Android has its own ICU, which doesn't 100% map to UsingLimitedCultures - public static bool SupportFullGlobalizationData => PlatformDetection.IsNotUsingLimitedCultures || PlatformDetection.IsAndroid; + // Browser uses JS to get the NativeName that is missing in ICU + public static bool SupportFullGlobalizationData => !PlatformDetection.IsWasi || PlatformDetection.IsHybridGlobalizationOnApplePlatform; public static IEnumerable EnglishName_TestData() { yield return new object[] { CultureInfo.CurrentCulture.Name, CultureInfo.CurrentCulture.EnglishName }; - if (SupportFullGlobalizationData || PlatformDetection.IsHybridGlobalizationOnApplePlatform) + if (SupportFullGlobalizationData) { yield return new object[] { "en-US", "English (United States)" }; yield return new object[] { "fr-FR", "French (France)" }; @@ -41,12 +42,12 @@ public void EnglishName(string name, string expected) public void ChineseNeutralEnglishName() { CultureInfo ci = new CultureInfo("zh-Hans"); - Assert.True(ci.EnglishName == "Chinese (Simplified)" || ci.EnglishName == "Chinese, Simplified", - $"'{ci.EnglishName}' not equal to `Chinese (Simplified)` nor `Chinese, Simplified`"); + Assert.True(ci.EnglishName == "Chinese (Simplified)" || ci.EnglishName == "Chinese, Simplified" || ci.EnglishName == "Simplified Chinese", + $"'{ci.EnglishName}' not equal to `Chinese (Simplified)` nor `Chinese, Simplified` nor `Simplified Chinese`"); ci = new CultureInfo("zh-HanT"); - Assert.True(ci.EnglishName == "Chinese (Traditional)" || ci.EnglishName == "Chinese, Traditional", - $"'{ci.EnglishName}' not equal to `Chinese (Traditional)` nor `Chinese, Traditional`"); + Assert.True(ci.EnglishName == "Chinese (Traditional)" || ci.EnglishName == "Chinese, Traditional" || ci.EnglishName == "Traditional Chinese", + $"'{ci.EnglishName}' not equal to `Chinese (Traditional)` nor `Chinese, Traditional` nor `Traditional Chinese`"); } } } diff --git a/src/libraries/System.Runtime/tests/System.Globalization.Tests/CultureInfo/CultureInfoNativeName.cs b/src/libraries/System.Runtime/tests/System.Globalization.Tests/CultureInfo/CultureInfoNativeName.cs index 9a834dbf2d1db..c4fcc4e2ab495 100644 --- a/src/libraries/System.Runtime/tests/System.Globalization.Tests/CultureInfo/CultureInfoNativeName.cs +++ b/src/libraries/System.Runtime/tests/System.Globalization.Tests/CultureInfo/CultureInfoNativeName.cs @@ -13,7 +13,8 @@ public static IEnumerable NativeName_TestData() yield return new object[] { CultureInfo.CurrentCulture.Name, CultureInfo.CurrentCulture.NativeName }; // Android has its own ICU, which doesn't 100% map to UsingLimitedCultures - if (PlatformDetection.IsNotUsingLimitedCultures || PlatformDetection.IsAndroid || PlatformDetection.IsHybridGlobalizationOnApplePlatform) + // Browser uses JS to get the NativeName that is missing in ICU + if (!PlatformDetection.IsWasi || PlatformDetection.IsHybridGlobalizationOnApplePlatform) { yield return new object[] { "en-US", "English (United States)" }; yield return new object[] { "en-CA", "English (Canada)" }; @@ -21,7 +22,7 @@ public static IEnumerable NativeName_TestData() } else { - // Mobile / Browser ICU doesn't contain CultureInfo.NativeName + // WASI's ICU doesn't contain CultureInfo.NativeName yield return new object[] { "en-US", "en (US)" }; yield return new object[] { "en-CA", "en (CA)" }; } diff --git a/src/libraries/System.Runtime/tests/System.Globalization.Tests/System/Globalization/RegionInfoTests.cs b/src/libraries/System.Runtime/tests/System.Globalization.Tests/System/Globalization/RegionInfoTests.cs index e05fd4e1a7422..602088d48e5f4 100644 --- a/src/libraries/System.Runtime/tests/System.Globalization.Tests/System/Globalization/RegionInfoTests.cs +++ b/src/libraries/System.Runtime/tests/System.Globalization.Tests/System/Globalization/RegionInfoTests.cs @@ -12,6 +12,8 @@ namespace System.Globalization.Tests { public class RegionInfoPropertyTests { + public static bool SupportFullGlobalizationData => !PlatformDetection.IsWasi || PlatformDetection.IsHybridGlobalizationOnApplePlatform; + [Theory] [InlineData("US", "US", "US")] [InlineData("IT", "IT", "IT")] @@ -112,7 +114,8 @@ public void DisplayName(string name, string expected) public static IEnumerable NativeName_TestData() { // Android has its own ICU, which doesn't 100% map to UsingLimitedCultures - if (PlatformDetection.IsNotUsingLimitedCultures || PlatformDetection.IsAndroid || PlatformDetection.IsHybridGlobalizationOnApplePlatform) + // Browser uses JS to get the NativeName that is missing in ICU + if (SupportFullGlobalizationData) { yield return new object[] { "GB", "United Kingdom" }; yield return new object[] { "SE", "Sverige" }; @@ -120,7 +123,7 @@ public static IEnumerable NativeName_TestData() } else { - // Browser's ICU doesn't contain RegionInfo.NativeName + // WASI's ICU doesn't contain RegionInfo.NativeName yield return new object[] { "GB", "GB" }; yield return new object[] { "SE", "SE" }; yield return new object[] { "FR", "FR" }; @@ -134,10 +137,11 @@ public void NativeName(string name, string expected) Assert.Equal(expected, new RegionInfo(name).NativeName); } - public static IEnumerable EnglishName_TestData() + public static IEnumerable EnglishName_TestData() // this might be failing, in that case - fix it or block it { // Android has its own ICU, which doesn't 100% map to UsingLimitedCultures - if (PlatformDetection.IsNotUsingLimitedCultures || PlatformDetection.IsAndroid || PlatformDetection.IsHybridGlobalizationOnApplePlatform) + // Browser uses JS to get the NativeName that is missing in ICU + if (SupportFullGlobalizationData) { yield return new object[] { "en-US", new string[] { "United States" } }; yield return new object[] { "US", new string[] { "United States" } }; @@ -146,7 +150,7 @@ public static IEnumerable EnglishName_TestData() } else { - // Browser's ICU doesn't contain RegionInfo.EnglishName + // WASI's ICU doesn't contain RegionInfo.EnglishName yield return new object[] { "en-US", new string[] { "US" } }; yield return new object[] { "US", new string[] { "US" } }; yield return new object[] { "zh-CN", new string[] { "CN" }}; diff --git a/src/mono/browser/runtime/corebindings.c b/src/mono/browser/runtime/corebindings.c index d37da93b57b4d..a4c1c0a7fb0c5 100644 --- a/src/mono/browser/runtime/corebindings.c +++ b/src/mono/browser/runtime/corebindings.c @@ -64,7 +64,7 @@ extern mono_bool mono_wasm_starts_with (MonoString **culture, const uint16_t* st extern mono_bool mono_wasm_ends_with (MonoString **culture, const uint16_t* str1, int32_t str1Length, const uint16_t* str2, int32_t str2Length, int32_t options, int *is_exception, MonoObject** ex_result); extern int mono_wasm_index_of (MonoString **culture, const uint16_t* str1, int32_t str1Length, const uint16_t* str2, int32_t str2Length, int32_t options, mono_bool fromBeginning, int *is_exception, MonoObject** ex_result); extern int mono_wasm_get_calendar_info (MonoString **culture, int32_t calendarId, const uint16_t* result, int32_t resultLength, int *is_exception, MonoObject** ex_result); -extern int mono_wasm_get_native_display_name (MonoString **locale, MonoString **culture, const uint16_t* result, int32_t resultLength, int *is_exception, MonoObject** ex_result); +extern int mono_wasm_get_locale_info (MonoString **locale, MonoString **culture, const uint16_t* result, int32_t resultLength, int *is_exception, MonoObject** ex_result); extern int mono_wasm_get_culture_info (MonoString **culture, const uint16_t* result, int32_t resultLength, int *is_exception, MonoObject** ex_result); extern int mono_wasm_get_first_day_of_week (MonoString **culture, int *is_exception, MonoObject** ex_result); extern int mono_wasm_get_first_week_of_year (MonoString **culture, int *is_exception, MonoObject** ex_result); @@ -106,7 +106,7 @@ void bindings_initialize_internals (void) mono_add_internal_call ("Interop/JsGlobalization::EndsWith", mono_wasm_ends_with); mono_add_internal_call ("Interop/JsGlobalization::IndexOf", mono_wasm_index_of); mono_add_internal_call ("Interop/JsGlobalization::GetCalendarInfo", mono_wasm_get_calendar_info); - mono_add_internal_call ("Interop/JsGlobalization::GetNativeName", mono_wasm_get_native_display_name); + mono_add_internal_call ("Interop/JsGlobalization::GetLocaleInfo", mono_wasm_get_locale_info); mono_add_internal_call ("Interop/JsGlobalization::GetCultureInfo", mono_wasm_get_culture_info); mono_add_internal_call ("Interop/JsGlobalization::GetFirstDayOfWeek", mono_wasm_get_first_day_of_week); mono_add_internal_call ("Interop/JsGlobalization::GetFirstWeekOfYear", mono_wasm_get_first_week_of_year); diff --git a/src/mono/browser/runtime/exports-binding.ts b/src/mono/browser/runtime/exports-binding.ts index cbd07f066217b..af129e772c702 100644 --- a/src/mono/browser/runtime/exports-binding.ts +++ b/src/mono/browser/runtime/exports-binding.ts @@ -22,7 +22,7 @@ import { mono_wasm_compare_string, mono_wasm_ends_with, mono_wasm_starts_with, m import { mono_wasm_get_calendar_info } from "./hybrid-globalization/calendar"; import { mono_wasm_get_culture_info } from "./hybrid-globalization/culture-info"; -import { mono_wasm_get_native_display_name, mono_wasm_get_first_day_of_week, mono_wasm_get_first_week_of_year } from "./hybrid-globalization/locales"; +import { mono_wasm_get_locale_info, mono_wasm_get_first_day_of_week, mono_wasm_get_first_week_of_year } from "./hybrid-globalization/locales"; import { mono_wasm_browser_entropy } from "./crypto"; import { mono_wasm_cancel_promise } from "./cancelable-promise"; @@ -107,7 +107,7 @@ export const mono_wasm_imports = [ mono_wasm_index_of, mono_wasm_get_calendar_info, mono_wasm_get_culture_info, - mono_wasm_get_native_display_name, + mono_wasm_get_locale_info, mono_wasm_get_first_day_of_week, mono_wasm_get_first_week_of_year, ]; diff --git a/src/mono/browser/runtime/hybrid-globalization/helpers.ts b/src/mono/browser/runtime/hybrid-globalization/helpers.ts index 1590936d8eacc..4cd1b9f4eb242 100644 --- a/src/mono/browser/runtime/hybrid-globalization/helpers.ts +++ b/src/mono/browser/runtime/hybrid-globalization/helpers.ts @@ -25,9 +25,9 @@ export function normalizeLocale(locale: string | null) const canonicalLocales = (Intl as any).getCanonicalLocales(locale.replace("_", "-")); return canonicalLocales.length > 0 ? canonicalLocales[0] : undefined; } - catch(ex: any) + catch { - throw new Error(`Get culture info failed for culture = ${locale} with error: ${ex}`); + return undefined; } } diff --git a/src/mono/browser/runtime/hybrid-globalization/locales.ts b/src/mono/browser/runtime/hybrid-globalization/locales.ts index 0841a8e02f5bf..d234810383e6e 100644 --- a/src/mono/browser/runtime/hybrid-globalization/locales.ts +++ b/src/mono/browser/runtime/hybrid-globalization/locales.ts @@ -6,29 +6,81 @@ import { mono_wasm_new_external_root } from "../roots"; import { monoStringToString, stringToUTF16 } from "../strings"; import { Int32Ptr } from "../types/emscripten"; import { MonoObject, MonoObjectRef, MonoString, MonoStringRef } from "../types/internal"; -import { normalizeLocale } from "./helpers"; +import { OUTER_SEPARATOR, normalizeLocale } from "./helpers"; -export function mono_wasm_get_native_display_name(locale: MonoStringRef, culture: MonoStringRef, dst: number, dstLength: number, isException: Int32Ptr, exAddress: MonoObjectRef) : number +export function mono_wasm_get_locale_info(culture: MonoStringRef, locale: MonoStringRef, dst: number, dstLength: number, isException: Int32Ptr, exAddress: MonoObjectRef) : number { const localeRoot = mono_wasm_new_external_root(locale), cultureRoot = mono_wasm_new_external_root(culture), exceptionRoot = mono_wasm_new_external_root(exAddress); try { - const localeName = monoStringToString(localeRoot); - const cultureName = monoStringToString(cultureRoot); + const localeNameOriginal = monoStringToString(localeRoot); + const localeName = normalizeLocale(localeNameOriginal); + if (!localeName && localeNameOriginal) + { + // handle non-standard or malformed locales by forwarding the locale code + stringToUTF16(dst, dst + 2 * localeNameOriginal.length, localeNameOriginal); + wrap_no_error_root(isException, exceptionRoot); + return localeNameOriginal.length; + } + const cultureNameOriginal = monoStringToString(cultureRoot); + const cultureName = normalizeLocale(cultureNameOriginal); + if (!localeName || !cultureName) - throw new Error("Locale or culture name is null or empty."); + throw new Error(`Locale or culture name is null or empty. localeName=${localeName}, cultureName=${cultureName}`); - const [language, region] = cultureName.split("-"); - const languageName = new Intl.DisplayNames([localeName], {type: "language"}).of(language); - const regionName = region ? new Intl.DisplayNames([localeName], {type: "region"}).of(region) : undefined; - const result = region ? `${languageName} (${regionName})` : languageName; + const localeParts = localeName.split("-"); // de-DE-u-co-phonebk-u-xx + // cultureName can be in a form of: + // 1) "language", e.g. "zh" + // 2) "language-region", e.g. "zn-CN" + // 3) "language-script-region", e.g. "zh-Hans-CN" + // 4) "language-script", e.g. "zh-Hans" (served in the catch block below) + let languageName, regionName; + try + { + const region = localeParts.length > 1 ? localeParts.pop() : undefined; + // this line might fail if form 4 from the comment above is used: + regionName = region ? new Intl.DisplayNames([cultureName], {type: "region"}).of(region) : undefined; + const language = localeParts.join("-"); + languageName = new Intl.DisplayNames([cultureName], {type: "language"}).of(language); + } + catch (error) + { + if (error instanceof RangeError && error.message === "invalid_argument") + { + // it failed from this reason then cultureName is in a form "language-script", without region + try + { + languageName = new Intl.DisplayNames([cultureName], {type: "language"}).of(localeName); + } + catch (error) + { + if (error instanceof RangeError && error.message === "invalid_argument" && localeNameOriginal) + { + // handle non-standard or malformed locales by forwarding the locale code, e.g. "xx-u-xx" + stringToUTF16(dst, dst + 2 * localeNameOriginal.length, localeNameOriginal); + wrap_no_error_root(isException, exceptionRoot); + return localeNameOriginal.length; + } + throw error; + } + } + else + { + throw error; + } + } + const localeInfo = { + LanguageName: languageName, + RegionName: regionName, + }; + const result = Object.values(localeInfo).join(OUTER_SEPARATOR); if (!result) - throw new Error(`Native display name for culture=${cultureName} is null or empty.`); + throw new Error(`Locale info for locale=${localeName} is null or empty.`); if (result.length > dstLength) - throw new Error(`Native display name for culture=${cultureName} exceeds length of ${dstLength}.`); + throw new Error(`Locale info for locale=${localeName} exceeds length of ${dstLength}.`); stringToUTF16(dst, dst + 2 * result.length, result); wrap_no_error_root(isException, exceptionRoot); From dceed892319da60855df33197f33fb796486f16a Mon Sep 17 00:00:00 2001 From: Ilona Tomkowicz <32700855+ilonatommy@users.noreply.github.com> Date: Wed, 20 Mar 2024 18:50:38 +0000 Subject: [PATCH 05/18] Ubnblock fixed test. --- .../System/Globalization/RegionInfoTests.cs | 1 - src/mono/browser/runtime/hybrid-globalization/locales.ts | 2 +- 2 files changed, 1 insertion(+), 2 deletions(-) diff --git a/src/libraries/System.Runtime/tests/System.Globalization.Tests/System/Globalization/RegionInfoTests.cs b/src/libraries/System.Runtime/tests/System.Globalization.Tests/System/Globalization/RegionInfoTests.cs index 602088d48e5f4..b5da114ae4ba9 100644 --- a/src/libraries/System.Runtime/tests/System.Globalization.Tests/System/Globalization/RegionInfoTests.cs +++ b/src/libraries/System.Runtime/tests/System.Globalization.Tests/System/Globalization/RegionInfoTests.cs @@ -102,7 +102,6 @@ public void ValidateUsingCasedRegionName(string regionName) [Theory] [InlineData("en-US", "United States")] [OuterLoop("May fail on machines with multiple language packs installed")] // see https://github.com/dotnet/runtime/issues/30132 - [ActiveIssue("https://github.com/dotnet/runtime/issues/45951", TestPlatforms.Browser)] public void DisplayName(string name, string expected) { using (new ThreadCultureChange(null, new CultureInfo(name))) diff --git a/src/mono/browser/runtime/hybrid-globalization/locales.ts b/src/mono/browser/runtime/hybrid-globalization/locales.ts index d234810383e6e..821ebb3890397 100644 --- a/src/mono/browser/runtime/hybrid-globalization/locales.ts +++ b/src/mono/browser/runtime/hybrid-globalization/locales.ts @@ -29,7 +29,7 @@ export function mono_wasm_get_locale_info(culture: MonoStringRef, locale: MonoSt if (!localeName || !cultureName) throw new Error(`Locale or culture name is null or empty. localeName=${localeName}, cultureName=${cultureName}`); - const localeParts = localeName.split("-"); // de-DE-u-co-phonebk-u-xx + const localeParts = localeName.split("-"); // cultureName can be in a form of: // 1) "language", e.g. "zh" // 2) "language-region", e.g. "zn-CN" From d516e2dada889d17c35751f7e37d9450ed90f026 Mon Sep 17 00:00:00 2001 From: Ilona Tomkowicz <32700855+ilonatommy@users.noreply.github.com> Date: Thu, 21 Mar 2024 09:16:45 +0000 Subject: [PATCH 06/18] MT does not work with HG well, revet MT to original way of working. --- .../src/System/Globalization/CultureData.Icu.cs | 4 ++-- .../src/System/Globalization/CultureData.cs | 2 +- .../CultureInfo/CultureInfoEnglishName.cs | 6 +++--- .../CultureInfo/CultureInfoNames.cs | 6 +++--- .../CultureInfo/CultureInfoNativeName.cs | 10 ++++++---- .../System/Globalization/RegionInfoTests.cs | 11 ++++------- 6 files changed, 19 insertions(+), 20 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 32d26e35433fb..31cc5bdd60155 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 @@ -203,7 +203,7 @@ private string IcuGetLocaleInfo(LocaleStringData type, string? uiCultureName = n Debug.Assert(!GlobalizationMode.Invariant); Debug.Assert(!GlobalizationMode.UseNls); Debug.Assert(_sWindowsName != null, "[CultureData.IcuGetLocaleInfo] Expected _sWindowsName to be populated already"); -#if TARGET_BROWSER +#if TARGET_BROWSER && !FEATURE_WASM_MANAGED_THREADS if (type == LocaleStringData.NativeDisplayName) { return JSGetNativeDisplayName(_sWindowsName, uiCultureName ?? _sWindowsName); @@ -310,7 +310,7 @@ private unsafe string IcuGetTimeFormatString(bool shortFormat) private string IcuGetLanguageDisplayName(string cultureName) { -#if TARGET_BROWSER +#if TARGET_BROWSER && !FEATURE_WASM_MANAGED_THREADS return JSGetNativeDisplayName(CultureInfo.CurrentUICulture.Name, cultureName); #else return IcuGetLocaleInfo(cultureName, LocaleStringData.LocalizedDisplayName, CultureInfo.CurrentUICulture.Name); diff --git a/src/libraries/System.Private.CoreLib/src/System/Globalization/CultureData.cs b/src/libraries/System.Private.CoreLib/src/System/Globalization/CultureData.cs index 7a543a9ecacdd..47f87734558a1 100644 --- a/src/libraries/System.Private.CoreLib/src/System/Globalization/CultureData.cs +++ b/src/libraries/System.Private.CoreLib/src/System/Globalization/CultureData.cs @@ -816,7 +816,7 @@ private static string NormalizeCultureName(string name, out bool isNeutralName) { return null; } -#if TARGET_BROWSER +#if TARGET_BROWSER && !FEATURE_WASM_MANAGED_THREADS // populate fields for which ICU does not provide data in Hybrid mode if (GlobalizationMode.Hybrid && !string.IsNullOrEmpty(culture._sName)) { diff --git a/src/libraries/System.Runtime/tests/System.Globalization.Tests/CultureInfo/CultureInfoEnglishName.cs b/src/libraries/System.Runtime/tests/System.Globalization.Tests/CultureInfo/CultureInfoEnglishName.cs index 5d040857b5fd0..0868869622a63 100644 --- a/src/libraries/System.Runtime/tests/System.Globalization.Tests/CultureInfo/CultureInfoEnglishName.cs +++ b/src/libraries/System.Runtime/tests/System.Globalization.Tests/CultureInfo/CultureInfoEnglishName.cs @@ -9,8 +9,9 @@ namespace System.Globalization.Tests public class CultureInfoEnglishName { // Android has its own ICU, which doesn't 100% map to UsingLimitedCultures - // Browser uses JS to get the NativeName that is missing in ICU - public static bool SupportFullGlobalizationData => !PlatformDetection.IsWasi || PlatformDetection.IsHybridGlobalizationOnApplePlatform; + // Browser uses JS to get the NativeName that is missing in ICU (in the singlethreaded runtime only) + public static bool SupportFullGlobalizationData => + (!PlatformDetection.IsWasi || PlatformDetection.IsHybridGlobalizationOnApplePlatform) && !PlatformDetection.IsWasmThreadingSupported; public static IEnumerable EnglishName_TestData() { @@ -24,7 +25,6 @@ public static IEnumerable EnglishName_TestData() } else { - // Mobile / Browser ICU doesn't contain CultureInfo.EnglishName yield return new object[] { "en-US", "en (US)" }; yield return new object[] { "fr-FR", "fr (FR)" }; } diff --git a/src/libraries/System.Runtime/tests/System.Globalization.Tests/CultureInfo/CultureInfoNames.cs b/src/libraries/System.Runtime/tests/System.Globalization.Tests/CultureInfo/CultureInfoNames.cs index fdde2c84ab56b..b4ba35adb85e4 100644 --- a/src/libraries/System.Runtime/tests/System.Globalization.Tests/CultureInfo/CultureInfoNames.cs +++ b/src/libraries/System.Runtime/tests/System.Globalization.Tests/CultureInfo/CultureInfoNames.cs @@ -10,10 +10,10 @@ namespace System.Globalization.Tests { public class CultureInfoNames { + // Android has its own ICU, which doesn't 100% map to UsingLimitedCultures + // Browser uses JS to get the NativeName that is missing in ICU (in the singlethreaded runtime only) private static bool SupportFullIcuResources => - (PlatformDetection.IsNotMobile && PlatformDetection.IsIcuGlobalization) || - PlatformDetection.IsHybridGlobalizationOnApplePlatform || - PlatformDetection.IsBrowser; + !PlatformDetection.IsWasi && !PlatformDetection.IsAndroid && PlatformDetection.IsIcuGlobalization && !PlatformDetection.IsWasmThreadingSupported; public static IEnumerable SupportedCultures_TestData() { diff --git a/src/libraries/System.Runtime/tests/System.Globalization.Tests/CultureInfo/CultureInfoNativeName.cs b/src/libraries/System.Runtime/tests/System.Globalization.Tests/CultureInfo/CultureInfoNativeName.cs index c4fcc4e2ab495..c61f8364e20c8 100644 --- a/src/libraries/System.Runtime/tests/System.Globalization.Tests/CultureInfo/CultureInfoNativeName.cs +++ b/src/libraries/System.Runtime/tests/System.Globalization.Tests/CultureInfo/CultureInfoNativeName.cs @@ -8,13 +8,16 @@ namespace System.Globalization.Tests { public class CultureInfoNativeName { + // Android has its own ICU, which doesn't 100% map to UsingLimitedCultures + // Browser uses JS to get the NativeName that is missing in ICU (in the singlethreaded runtime only) + private static bool SupportFullIcuResources => + (!PlatformDetection.IsWasi || PlatformDetection.IsHybridGlobalizationOnApplePlatform) && !PlatformDetection.IsWasmThreadingSupported; + public static IEnumerable NativeName_TestData() { yield return new object[] { CultureInfo.CurrentCulture.Name, CultureInfo.CurrentCulture.NativeName }; - // Android has its own ICU, which doesn't 100% map to UsingLimitedCultures - // Browser uses JS to get the NativeName that is missing in ICU - if (!PlatformDetection.IsWasi || PlatformDetection.IsHybridGlobalizationOnApplePlatform) + if (SupportFullIcuResources) { yield return new object[] { "en-US", "English (United States)" }; yield return new object[] { "en-CA", "English (Canada)" }; @@ -22,7 +25,6 @@ public static IEnumerable NativeName_TestData() } else { - // WASI's ICU doesn't contain CultureInfo.NativeName yield return new object[] { "en-US", "en (US)" }; yield return new object[] { "en-CA", "en (CA)" }; } diff --git a/src/libraries/System.Runtime/tests/System.Globalization.Tests/System/Globalization/RegionInfoTests.cs b/src/libraries/System.Runtime/tests/System.Globalization.Tests/System/Globalization/RegionInfoTests.cs index b5da114ae4ba9..5783dd62b1997 100644 --- a/src/libraries/System.Runtime/tests/System.Globalization.Tests/System/Globalization/RegionInfoTests.cs +++ b/src/libraries/System.Runtime/tests/System.Globalization.Tests/System/Globalization/RegionInfoTests.cs @@ -12,7 +12,10 @@ namespace System.Globalization.Tests { public class RegionInfoPropertyTests { - public static bool SupportFullGlobalizationData => !PlatformDetection.IsWasi || PlatformDetection.IsHybridGlobalizationOnApplePlatform; + // Android has its own ICU, which doesn't 100% map to UsingLimitedCultures + // Browser uses JS to get the NativeName that is missing in ICU (in the singlethreaded runtime only) + public static bool SupportFullGlobalizationData => + (!PlatformDetection.IsWasi || PlatformDetection.IsHybridGlobalizationOnApplePlatform) && !PlatformDetection.IsWasmThreadingSupported; [Theory] [InlineData("US", "US", "US")] @@ -112,8 +115,6 @@ public void DisplayName(string name, string expected) public static IEnumerable NativeName_TestData() { - // Android has its own ICU, which doesn't 100% map to UsingLimitedCultures - // Browser uses JS to get the NativeName that is missing in ICU if (SupportFullGlobalizationData) { yield return new object[] { "GB", "United Kingdom" }; @@ -122,7 +123,6 @@ public static IEnumerable NativeName_TestData() } else { - // WASI's ICU doesn't contain RegionInfo.NativeName yield return new object[] { "GB", "GB" }; yield return new object[] { "SE", "SE" }; yield return new object[] { "FR", "FR" }; @@ -138,8 +138,6 @@ public void NativeName(string name, string expected) public static IEnumerable EnglishName_TestData() // this might be failing, in that case - fix it or block it { - // Android has its own ICU, which doesn't 100% map to UsingLimitedCultures - // Browser uses JS to get the NativeName that is missing in ICU if (SupportFullGlobalizationData) { yield return new object[] { "en-US", new string[] { "United States" } }; @@ -149,7 +147,6 @@ public void NativeName(string name, string expected) } else { - // WASI's ICU doesn't contain RegionInfo.EnglishName yield return new object[] { "en-US", new string[] { "US" } }; yield return new object[] { "US", new string[] { "US" } }; yield return new object[] { "zh-CN", new string[] { "CN" }}; From 20752cec30ceda6743583be85f685263d7bef603 Mon Sep 17 00:00:00 2001 From: Ilona Tomkowicz <32700855+ilonatommy@users.noreply.github.com> Date: Thu, 21 Mar 2024 14:47:20 +0000 Subject: [PATCH 07/18] Expect fixed version of NativeName in tests with single treaded runtime. --- .../wasm/Wasm.Build.Tests/Blazor/WorkloadRequiredTests.cs | 3 ++- src/mono/wasm/Wasm.Build.Tests/InvariantGlobalizationTests.cs | 4 +++- 2 files changed, 5 insertions(+), 2 deletions(-) diff --git a/src/mono/wasm/Wasm.Build.Tests/Blazor/WorkloadRequiredTests.cs b/src/mono/wasm/Wasm.Build.Tests/Blazor/WorkloadRequiredTests.cs index 0fd604cfea6e2..79d3921a9442b 100644 --- a/src/mono/wasm/Wasm.Build.Tests/Blazor/WorkloadRequiredTests.cs +++ b/src/mono/wasm/Wasm.Build.Tests/Blazor/WorkloadRequiredTests.cs @@ -141,10 +141,11 @@ await BlazorRunTest(new BlazorRunOptions() } else { + string nativeName = IsWorkloadWithMultiThreadingForDefaultFramework ? "es-ES" : "espa\u00F1ol (Espa\u00F1a)"; // MT does not use JS to get the full name Assert.DoesNotContain("Could not create es-ES culture", output); Assert.DoesNotContain("invalid culture", output); Assert.DoesNotContain("CurrentCulture.NativeName: Invariant Language (Invariant Country)", output); - Assert.Contains("es-ES: Is-LCID-InvariantCulture: False, NativeName: es (ES)", output); + Assert.Contains($"es-ES: Is-LCID-InvariantCulture: False, NativeName: {nativeName}", output); // ignoring the last line of the output which prints the current culture } diff --git a/src/mono/wasm/Wasm.Build.Tests/InvariantGlobalizationTests.cs b/src/mono/wasm/Wasm.Build.Tests/InvariantGlobalizationTests.cs index 25f16fca67af4..7d43833e119e9 100644 --- a/src/mono/wasm/Wasm.Build.Tests/InvariantGlobalizationTests.cs +++ b/src/mono/wasm/Wasm.Build.Tests/InvariantGlobalizationTests.cs @@ -70,7 +70,9 @@ private void TestInvariantGlobalization(BuildArgs buildArgs, bool? invariantGlob else { string output = RunAndTestWasmApp(buildArgs, expectedExitCode: 42, host: host, id: id); - Assert.Contains("es-ES: Is Invariant LCID: False, NativeName: es (ES)", output); + Assert.Contains(IsWorkloadWithMultiThreadingForDefaultFramework ? + "es-ES: Is Invariant LCID: False, NativeName: es (ES)" : // MT does not use JS to get the full name + "es-ES: Is Invariant LCID: False, NativeName: espa\u00F1ol (Espa\u00F1a)", output); // ignoring the last line of the output which prints the current culture } From 6da6c971c662e716446e8188135bed4498bb7d11 Mon Sep 17 00:00:00 2001 From: Ilona Tomkowicz <32700855+ilonatommy@users.noreply.github.com> Date: Fri, 22 Mar 2024 08:03:13 +0000 Subject: [PATCH 08/18] =?UTF-8?q?Assert.Contains=20does=20not=20know=20tha?= =?UTF-8?q?t=20`\u00F1`=20is=20same=20as=20`=C3=B1`=20(it=20does=20not=20e?= =?UTF-8?q?valuate=20the=20string=20before=20comparison)=20-=20fix=20it=20?= =?UTF-8?q?with=20string=20interpolation.?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../wasm/Wasm.Build.Tests/InvariantGlobalizationTests.cs | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/src/mono/wasm/Wasm.Build.Tests/InvariantGlobalizationTests.cs b/src/mono/wasm/Wasm.Build.Tests/InvariantGlobalizationTests.cs index 7d43833e119e9..6a8abf9579846 100644 --- a/src/mono/wasm/Wasm.Build.Tests/InvariantGlobalizationTests.cs +++ b/src/mono/wasm/Wasm.Build.Tests/InvariantGlobalizationTests.cs @@ -69,10 +69,9 @@ private void TestInvariantGlobalization(BuildArgs buildArgs, bool? invariantGlob } else { + string nativeName = IsWorkloadWithMultiThreadingForDefaultFramework ? "es-ES" : "espa\u00F1ol (Espa\u00F1a)"; // MT does not use JS to get the full name string output = RunAndTestWasmApp(buildArgs, expectedExitCode: 42, host: host, id: id); - Assert.Contains(IsWorkloadWithMultiThreadingForDefaultFramework ? - "es-ES: Is Invariant LCID: False, NativeName: es (ES)" : // MT does not use JS to get the full name - "es-ES: Is Invariant LCID: False, NativeName: espa\u00F1ol (Espa\u00F1a)", output); + Assert.Contains($"es-ES: Is Invariant LCID: False, NativeName: {nativeName}", output); // ignoring the last line of the output which prints the current culture } From 4e8fe6fee51272fde3d009c7b10501e5ca615666 Mon Sep 17 00:00:00 2001 From: Ilona Tomkowicz <32700855+ilonatommy@users.noreply.github.com> Date: Fri, 22 Mar 2024 10:16:44 +0000 Subject: [PATCH 09/18] Avoid utf8 signs when using `Assert.Contains` --- .../wasm/Wasm.Build.Tests/InvariantGlobalizationTests.cs | 6 +++--- .../Wasm.Buid.Tests.Programs/InvariantGlobalization.cs | 6 +++--- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/src/mono/wasm/Wasm.Build.Tests/InvariantGlobalizationTests.cs b/src/mono/wasm/Wasm.Build.Tests/InvariantGlobalizationTests.cs index 6a8abf9579846..a1f47fc3f52e5 100644 --- a/src/mono/wasm/Wasm.Build.Tests/InvariantGlobalizationTests.cs +++ b/src/mono/wasm/Wasm.Build.Tests/InvariantGlobalizationTests.cs @@ -64,14 +64,14 @@ private void TestInvariantGlobalization(BuildArgs buildArgs, bool? invariantGlob if (invariantGlobalization == true) { string output = RunAndTestWasmApp(buildArgs, expectedExitCode: 42, host: host, id: id); - Assert.Contains("Could not create es-ES culture", output); + Assert.Contains("Could not create de-DE culture", output); Assert.Contains("CurrentCulture.NativeName: Invariant Language (Invariant Country)", output); } else { - string nativeName = IsWorkloadWithMultiThreadingForDefaultFramework ? "es-ES" : "espa\u00F1ol (Espa\u00F1a)"; // MT does not use JS to get the full name + string nativeName = IsWorkloadWithMultiThreadingForDefaultFramework ? "de-DE" : "Deutsch (Deutschland)"; // MT does not use JS to get the full name string output = RunAndTestWasmApp(buildArgs, expectedExitCode: 42, host: host, id: id); - Assert.Contains($"es-ES: Is Invariant LCID: False, NativeName: {nativeName}", output); + Assert.Contains($"de-DE: Is Invariant LCID: False, NativeName: {nativeName}", output); // ignoring the last line of the output which prints the current culture } diff --git a/src/mono/wasm/testassets/Wasm.Buid.Tests.Programs/InvariantGlobalization.cs b/src/mono/wasm/testassets/Wasm.Buid.Tests.Programs/InvariantGlobalization.cs index 9237110cbc498..593f7d187179f 100644 --- a/src/mono/wasm/testassets/Wasm.Buid.Tests.Programs/InvariantGlobalization.cs +++ b/src/mono/wasm/testassets/Wasm.Buid.Tests.Programs/InvariantGlobalization.cs @@ -4,12 +4,12 @@ // https://github.com/dotnet/runtime/blob/main/docs/design/features/globalization-invariant-mode.md#cultures-and-culture-data try { - CultureInfo culture = new ("es-ES", false); - Console.WriteLine($"es-ES: Is Invariant LCID: {culture.LCID == CultureInfo.InvariantCulture.LCID}, NativeName: {culture.NativeName}"); + CultureInfo culture = new ("de-DE", false); + Console.WriteLine($"de-DE: Is Invariant LCID: {culture.LCID == CultureInfo.InvariantCulture.LCID}, NativeName: {culture.NativeName}"); } catch (CultureNotFoundException cnfe) { - Console.WriteLine($"Could not create es-ES culture: {cnfe.Message}"); + Console.WriteLine($"Could not create de-DE culture: {cnfe.Message}"); } Console.WriteLine($"CurrentCulture.NativeName: {CultureInfo.CurrentCulture.NativeName}"); From 3bfe422f9cee8fc57d3f1447997c98942fd5b3fa Mon Sep 17 00:00:00 2001 From: Ilona Tomkowicz <32700855+ilonatommy@users.noreply.github.com> Date: Fri, 22 Mar 2024 13:12:06 +0000 Subject: [PATCH 10/18] Check exactly what part is not found on helix machine. --- src/mono/wasm/Wasm.Build.Tests/InvariantGlobalizationTests.cs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/mono/wasm/Wasm.Build.Tests/InvariantGlobalizationTests.cs b/src/mono/wasm/Wasm.Build.Tests/InvariantGlobalizationTests.cs index a1f47fc3f52e5..a01fdd2b5e354 100644 --- a/src/mono/wasm/Wasm.Build.Tests/InvariantGlobalizationTests.cs +++ b/src/mono/wasm/Wasm.Build.Tests/InvariantGlobalizationTests.cs @@ -71,7 +71,8 @@ private void TestInvariantGlobalization(BuildArgs buildArgs, bool? invariantGlob { string nativeName = IsWorkloadWithMultiThreadingForDefaultFramework ? "de-DE" : "Deutsch (Deutschland)"; // MT does not use JS to get the full name string output = RunAndTestWasmApp(buildArgs, expectedExitCode: 42, host: host, id: id); - Assert.Contains($"de-DE: Is Invariant LCID: False, NativeName: {nativeName}", output); + Assert.Contains("de-DE: Is Invariant LCID: False", output); + Assert.Contains($"NativeName: {nativeName}", output); // ignoring the last line of the output which prints the current culture } From 2f9b9ecc60ef6e34711194cc8f2083ef4ff3a4d1 Mon Sep 17 00:00:00 2001 From: Ilona Tomkowicz <32700855+ilonatommy@users.noreply.github.com> Date: Fri, 22 Mar 2024 16:34:58 +0000 Subject: [PATCH 11/18] `IsWorkloadWithMultiThreadingForDefaultFramework` was true even for ST, change to `FEATURE_WASM_MANAGED_THREADS`. --- .../wasm/Wasm.Build.Tests/InvariantGlobalizationTests.cs | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/src/mono/wasm/Wasm.Build.Tests/InvariantGlobalizationTests.cs b/src/mono/wasm/Wasm.Build.Tests/InvariantGlobalizationTests.cs index a01fdd2b5e354..16606e6d11da5 100644 --- a/src/mono/wasm/Wasm.Build.Tests/InvariantGlobalizationTests.cs +++ b/src/mono/wasm/Wasm.Build.Tests/InvariantGlobalizationTests.cs @@ -69,10 +69,13 @@ private void TestInvariantGlobalization(BuildArgs buildArgs, bool? invariantGlob } else { - string nativeName = IsWorkloadWithMultiThreadingForDefaultFramework ? "de-DE" : "Deutsch (Deutschland)"; // MT does not use JS to get the full name string output = RunAndTestWasmApp(buildArgs, expectedExitCode: 42, host: host, id: id); Assert.Contains("de-DE: Is Invariant LCID: False", output); - Assert.Contains($"NativeName: {nativeName}", output); +#if FEATURE_WASM_MANAGED_THREADS + Assert.Contains("NativeName: de-DE", output); // MT does not use JS to get the full name +#else + Assert.Contains("NativeName: Deutsch (Deutschland)", output); +#endif // ignoring the last line of the output which prints the current culture } From 9eeffd4d77d7e7331454ea9f3295e76c39f71a18 Mon Sep 17 00:00:00 2001 From: Ilona Tomkowicz <32700855+ilonatommy@users.noreply.github.com> Date: Sat, 23 Mar 2024 07:19:58 +0000 Subject: [PATCH 12/18] Changing locale code was not necessary. --- .../wasm/Wasm.Build.Tests/InvariantGlobalizationTests.cs | 8 ++++---- .../Wasm.Buid.Tests.Programs/InvariantGlobalization.cs | 4 ++-- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/src/mono/wasm/Wasm.Build.Tests/InvariantGlobalizationTests.cs b/src/mono/wasm/Wasm.Build.Tests/InvariantGlobalizationTests.cs index 16606e6d11da5..bd9ccf8434c57 100644 --- a/src/mono/wasm/Wasm.Build.Tests/InvariantGlobalizationTests.cs +++ b/src/mono/wasm/Wasm.Build.Tests/InvariantGlobalizationTests.cs @@ -64,17 +64,17 @@ private void TestInvariantGlobalization(BuildArgs buildArgs, bool? invariantGlob if (invariantGlobalization == true) { string output = RunAndTestWasmApp(buildArgs, expectedExitCode: 42, host: host, id: id); - Assert.Contains("Could not create de-DE culture", output); + Assert.Contains("Could not create es-ES culture", output); Assert.Contains("CurrentCulture.NativeName: Invariant Language (Invariant Country)", output); } else { string output = RunAndTestWasmApp(buildArgs, expectedExitCode: 42, host: host, id: id); - Assert.Contains("de-DE: Is Invariant LCID: False", output); + Assert.Contains("es-ES: Is Invariant LCID: False", output); #if FEATURE_WASM_MANAGED_THREADS - Assert.Contains("NativeName: de-DE", output); // MT does not use JS to get the full name + Assert.Contains("NativeName: es-ES", output); // MT does not use JS to get the full name #else - Assert.Contains("NativeName: Deutsch (Deutschland)", output); + Assert.Contains("NativeName: espa\u00F1ol (Espa\u00F1a)", output); #endif // ignoring the last line of the output which prints the current culture diff --git a/src/mono/wasm/testassets/Wasm.Buid.Tests.Programs/InvariantGlobalization.cs b/src/mono/wasm/testassets/Wasm.Buid.Tests.Programs/InvariantGlobalization.cs index 593f7d187179f..10ea73d7398b9 100644 --- a/src/mono/wasm/testassets/Wasm.Buid.Tests.Programs/InvariantGlobalization.cs +++ b/src/mono/wasm/testassets/Wasm.Buid.Tests.Programs/InvariantGlobalization.cs @@ -4,12 +4,12 @@ // https://github.com/dotnet/runtime/blob/main/docs/design/features/globalization-invariant-mode.md#cultures-and-culture-data try { - CultureInfo culture = new ("de-DE", false); + CultureInfo culture = new ("es-ES", false); Console.WriteLine($"de-DE: Is Invariant LCID: {culture.LCID == CultureInfo.InvariantCulture.LCID}, NativeName: {culture.NativeName}"); } catch (CultureNotFoundException cnfe) { - Console.WriteLine($"Could not create de-DE culture: {cnfe.Message}"); + Console.WriteLine($"Could not create es-ES culture: {cnfe.Message}"); } Console.WriteLine($"CurrentCulture.NativeName: {CultureInfo.CurrentCulture.NativeName}"); From 1bf5b959407228d098821988befe3d16667db6f8 Mon Sep 17 00:00:00 2001 From: Ilona Tomkowicz <32700855+ilonatommy@users.noreply.github.com> Date: Sat, 23 Mar 2024 07:20:55 +0000 Subject: [PATCH 13/18] Fix --- .../Wasm.Buid.Tests.Programs/InvariantGlobalization.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/mono/wasm/testassets/Wasm.Buid.Tests.Programs/InvariantGlobalization.cs b/src/mono/wasm/testassets/Wasm.Buid.Tests.Programs/InvariantGlobalization.cs index 10ea73d7398b9..9237110cbc498 100644 --- a/src/mono/wasm/testassets/Wasm.Buid.Tests.Programs/InvariantGlobalization.cs +++ b/src/mono/wasm/testassets/Wasm.Buid.Tests.Programs/InvariantGlobalization.cs @@ -5,7 +5,7 @@ try { CultureInfo culture = new ("es-ES", false); - Console.WriteLine($"de-DE: Is Invariant LCID: {culture.LCID == CultureInfo.InvariantCulture.LCID}, NativeName: {culture.NativeName}"); + Console.WriteLine($"es-ES: Is Invariant LCID: {culture.LCID == CultureInfo.InvariantCulture.LCID}, NativeName: {culture.NativeName}"); } catch (CultureNotFoundException cnfe) { From bf5bc9a91be8036410cccf6b03ec020b515e038e Mon Sep 17 00:00:00 2001 From: Ilona Tomkowicz <32700855+ilonatommy@users.noreply.github.com> Date: Mon, 25 Mar 2024 09:07:23 +0000 Subject: [PATCH 14/18] WBT don't run in MT, unless we create a MT app in WBT. --- .../wasm/Wasm.Build.Tests/Blazor/WorkloadRequiredTests.cs | 4 ++-- src/mono/wasm/Wasm.Build.Tests/InvariantGlobalizationTests.cs | 4 ---- 2 files changed, 2 insertions(+), 6 deletions(-) diff --git a/src/mono/wasm/Wasm.Build.Tests/Blazor/WorkloadRequiredTests.cs b/src/mono/wasm/Wasm.Build.Tests/Blazor/WorkloadRequiredTests.cs index 79d3921a9442b..d0957cb54321e 100644 --- a/src/mono/wasm/Wasm.Build.Tests/Blazor/WorkloadRequiredTests.cs +++ b/src/mono/wasm/Wasm.Build.Tests/Blazor/WorkloadRequiredTests.cs @@ -141,11 +141,11 @@ await BlazorRunTest(new BlazorRunOptions() } else { - string nativeName = IsWorkloadWithMultiThreadingForDefaultFramework ? "es-ES" : "espa\u00F1ol (Espa\u00F1a)"; // MT does not use JS to get the full name Assert.DoesNotContain("Could not create es-ES culture", output); Assert.DoesNotContain("invalid culture", output); Assert.DoesNotContain("CurrentCulture.NativeName: Invariant Language (Invariant Country)", output); - Assert.Contains($"es-ES: Is-LCID-InvariantCulture: False, NativeName: {nativeName}", output); + Assert.Contains("es-ES: Is Invariant LCID: False", output); + Assert.Contains("NativeName: espa\u00F1ol (Espa\u00F1a)", output); // ignoring the last line of the output which prints the current culture } diff --git a/src/mono/wasm/Wasm.Build.Tests/InvariantGlobalizationTests.cs b/src/mono/wasm/Wasm.Build.Tests/InvariantGlobalizationTests.cs index bd9ccf8434c57..911fa85012179 100644 --- a/src/mono/wasm/Wasm.Build.Tests/InvariantGlobalizationTests.cs +++ b/src/mono/wasm/Wasm.Build.Tests/InvariantGlobalizationTests.cs @@ -71,11 +71,7 @@ private void TestInvariantGlobalization(BuildArgs buildArgs, bool? invariantGlob { string output = RunAndTestWasmApp(buildArgs, expectedExitCode: 42, host: host, id: id); Assert.Contains("es-ES: Is Invariant LCID: False", output); -#if FEATURE_WASM_MANAGED_THREADS - Assert.Contains("NativeName: es-ES", output); // MT does not use JS to get the full name -#else Assert.Contains("NativeName: espa\u00F1ol (Espa\u00F1a)", output); -#endif // ignoring the last line of the output which prints the current culture } From 6d34695604fb80d3f7a2930845963454cd0e5216 Mon Sep 17 00:00:00 2001 From: Ilona Tomkowicz <32700855+ilonatommy@users.noreply.github.com> Date: Mon, 25 Mar 2024 10:05:19 +0000 Subject: [PATCH 15/18] wrong string --- src/mono/wasm/Wasm.Build.Tests/Blazor/WorkloadRequiredTests.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/mono/wasm/Wasm.Build.Tests/Blazor/WorkloadRequiredTests.cs b/src/mono/wasm/Wasm.Build.Tests/Blazor/WorkloadRequiredTests.cs index d0957cb54321e..a735e2af15e44 100644 --- a/src/mono/wasm/Wasm.Build.Tests/Blazor/WorkloadRequiredTests.cs +++ b/src/mono/wasm/Wasm.Build.Tests/Blazor/WorkloadRequiredTests.cs @@ -144,7 +144,7 @@ await BlazorRunTest(new BlazorRunOptions() Assert.DoesNotContain("Could not create es-ES culture", output); Assert.DoesNotContain("invalid culture", output); Assert.DoesNotContain("CurrentCulture.NativeName: Invariant Language (Invariant Country)", output); - Assert.Contains("es-ES: Is Invariant LCID: False", output); + Assert.Contains("es-ES: Is-LCID-InvariantCulture: False", output); Assert.Contains("NativeName: espa\u00F1ol (Espa\u00F1a)", output); // ignoring the last line of the output which prints the current culture From 05d43c713fa6f8d9c9bd53e9f365214cf0296315 Mon Sep 17 00:00:00 2001 From: Ilona Tomkowicz <32700855+ilonatommy@users.noreply.github.com> Date: Mon, 25 Mar 2024 15:01:34 +0000 Subject: [PATCH 16/18] Windows has problems with comparing utf8 by xunit. --- .../Wasm.Build.Tests/InvariantGlobalizationTests.cs | 3 +-- .../InvariantGlobalization.cs | 11 ++++++++++- 2 files changed, 11 insertions(+), 3 deletions(-) diff --git a/src/mono/wasm/Wasm.Build.Tests/InvariantGlobalizationTests.cs b/src/mono/wasm/Wasm.Build.Tests/InvariantGlobalizationTests.cs index 911fa85012179..816f088666731 100644 --- a/src/mono/wasm/Wasm.Build.Tests/InvariantGlobalizationTests.cs +++ b/src/mono/wasm/Wasm.Build.Tests/InvariantGlobalizationTests.cs @@ -69,9 +69,8 @@ private void TestInvariantGlobalization(BuildArgs buildArgs, bool? invariantGlob } else { - string output = RunAndTestWasmApp(buildArgs, expectedExitCode: 42, host: host, id: id); + string output = RunAndTestWasmApp(buildArgs, expectedExitCode: 42, host: host, id: id, args: "nativename=\"espa\u00F1ol (Espa\u00F1a)\""); Assert.Contains("es-ES: Is Invariant LCID: False", output); - Assert.Contains("NativeName: espa\u00F1ol (Espa\u00F1a)", output); // ignoring the last line of the output which prints the current culture } diff --git a/src/mono/wasm/testassets/Wasm.Buid.Tests.Programs/InvariantGlobalization.cs b/src/mono/wasm/testassets/Wasm.Buid.Tests.Programs/InvariantGlobalization.cs index 9237110cbc498..c7b1219b6aabd 100644 --- a/src/mono/wasm/testassets/Wasm.Buid.Tests.Programs/InvariantGlobalization.cs +++ b/src/mono/wasm/testassets/Wasm.Buid.Tests.Programs/InvariantGlobalization.cs @@ -1,11 +1,20 @@ using System; using System.Globalization; +using System.Linq; // https://github.com/dotnet/runtime/blob/main/docs/design/features/globalization-invariant-mode.md#cultures-and-culture-data try { CultureInfo culture = new ("es-ES", false); - Console.WriteLine($"es-ES: Is Invariant LCID: {culture.LCID == CultureInfo.InvariantCulture.LCID}, NativeName: {culture.NativeName}"); + Console.WriteLine($"es-ES: Is Invariant LCID: {culture.LCID == CultureInfo.InvariantCulture.LCID}"); + + var nativeNameArg = args.FirstOrDefault(arg => arg.StartsWith("nativename=")); + if (nativeNameArg == null) + throw new ArgumentException($"When not in invariant mode, InvariantGlobalization.cs expects nativename argument with expected es-ES NativeName."); + string expectedNativeName = nativeNameArg.Substring(11).Trim('"'); // skip nativename= + string nativeName = culture.NativeName; + if (nativeName != expectedNativeName) + throw new ArgumentException($"Expected es-ES NativeName: {expectedNativeName}, but got: {nativeName}"); } catch (CultureNotFoundException cnfe) { From 697bba874124596259d1fe984d2824835498cbab Mon Sep 17 00:00:00 2001 From: Ilona Tomkowicz <32700855+ilonatommy@users.noreply.github.com> Date: Tue, 26 Mar 2024 08:16:27 +0000 Subject: [PATCH 17/18] Nit: correct comments. --- .../src/System/Globalization/CultureData.Browser.cs | 1 - .../System/Globalization/RegionInfoTests.cs | 2 +- src/mono/browser/runtime/hybrid-globalization/locales.ts | 2 +- 3 files changed, 2 insertions(+), 3 deletions(-) diff --git a/src/libraries/System.Private.CoreLib/src/System/Globalization/CultureData.Browser.cs b/src/libraries/System.Private.CoreLib/src/System/Globalization/CultureData.Browser.cs index e8f2927fb4d72..e27de06e5881f 100644 --- a/src/libraries/System.Private.CoreLib/src/System/Globalization/CultureData.Browser.cs +++ b/src/libraries/System.Private.CoreLib/src/System/Globalization/CultureData.Browser.cs @@ -25,7 +25,6 @@ private static CultureData JSInitLocaleInfo(string? localeName, CultureData cult } else { - // ToDo: make sure we do not re-ask for it // English locale info (culture._sEnglishLanguage, culture._sEnglishCountry) = culture.JSGetLocaleInfo("en-US", localeName); culture._sEnglishDisplayName = string.IsNullOrEmpty(culture._sEnglishCountry) ? diff --git a/src/libraries/System.Runtime/tests/System.Globalization.Tests/System/Globalization/RegionInfoTests.cs b/src/libraries/System.Runtime/tests/System.Globalization.Tests/System/Globalization/RegionInfoTests.cs index 5783dd62b1997..ee39b219adb3d 100644 --- a/src/libraries/System.Runtime/tests/System.Globalization.Tests/System/Globalization/RegionInfoTests.cs +++ b/src/libraries/System.Runtime/tests/System.Globalization.Tests/System/Globalization/RegionInfoTests.cs @@ -136,7 +136,7 @@ public void NativeName(string name, string expected) Assert.Equal(expected, new RegionInfo(name).NativeName); } - public static IEnumerable EnglishName_TestData() // this might be failing, in that case - fix it or block it + public static IEnumerable EnglishName_TestData() { if (SupportFullGlobalizationData) { diff --git a/src/mono/browser/runtime/hybrid-globalization/locales.ts b/src/mono/browser/runtime/hybrid-globalization/locales.ts index 821ebb3890397..5b7c82a8faa58 100644 --- a/src/mono/browser/runtime/hybrid-globalization/locales.ts +++ b/src/mono/browser/runtime/hybrid-globalization/locales.ts @@ -48,7 +48,7 @@ export function mono_wasm_get_locale_info(culture: MonoStringRef, locale: MonoSt { if (error instanceof RangeError && error.message === "invalid_argument") { - // it failed from this reason then cultureName is in a form "language-script", without region + // if it failed from this reason then cultureName is in a form "language-script", without region try { languageName = new Intl.DisplayNames([cultureName], {type: "language"}).of(localeName); From 728d82d1a328b53b1dfd6331fe662d75b3ddef4c Mon Sep 17 00:00:00 2001 From: Ilona Tomkowicz <32700855+ilonatommy@users.noreply.github.com> Date: Tue, 26 Mar 2024 08:50:33 +0000 Subject: [PATCH 18/18] Feedback --- .../Globalization/CultureData.Browser.cs | 32 +++++++++---------- .../src/System/Globalization/CultureData.cs | 2 +- 2 files changed, 17 insertions(+), 17 deletions(-) diff --git a/src/libraries/System.Private.CoreLib/src/System/Globalization/CultureData.Browser.cs b/src/libraries/System.Private.CoreLib/src/System/Globalization/CultureData.Browser.cs index e27de06e5881f..8a8edadaf326d 100644 --- a/src/libraries/System.Private.CoreLib/src/System/Globalization/CultureData.Browser.cs +++ b/src/libraries/System.Private.CoreLib/src/System/Globalization/CultureData.Browser.cs @@ -12,31 +12,31 @@ internal sealed partial class CultureData private const int CULTURE_INFO_BUFFER_LEN = 50; private const int LOCALE_INFO_BUFFER_LEN = 80; - private static CultureData JSInitLocaleInfo(string? localeName, CultureData culture) + private void JSInitLocaleInfo() { + string? localeName = _sName; if (string.IsNullOrEmpty(localeName)) { - culture._sEnglishLanguage = "Invariant Language"; - culture._sNativeLanguage = culture._sEnglishLanguage; - culture._sEnglishCountry = "Invariant Country"; - culture._sNativeCountry = culture._sEnglishCountry; - culture._sEnglishDisplayName = $"{culture._sEnglishLanguage} ({culture._sEnglishCountry})"; - culture._sNativeDisplayName = culture._sEnglishDisplayName; + _sEnglishLanguage = "Invariant Language"; + _sNativeLanguage = _sEnglishLanguage; + _sEnglishCountry = "Invariant Country"; + _sNativeCountry = _sEnglishCountry; + _sEnglishDisplayName = $"{_sEnglishLanguage} ({_sEnglishCountry})"; + _sNativeDisplayName = _sEnglishDisplayName; } else { // English locale info - (culture._sEnglishLanguage, culture._sEnglishCountry) = culture.JSGetLocaleInfo("en-US", localeName); - culture._sEnglishDisplayName = string.IsNullOrEmpty(culture._sEnglishCountry) ? - culture._sEnglishLanguage : - $"{culture._sEnglishLanguage} ({culture._sEnglishCountry})"; + (_sEnglishLanguage, _sEnglishCountry) = JSGetLocaleInfo("en-US", localeName); + _sEnglishDisplayName = string.IsNullOrEmpty(_sEnglishCountry) ? + _sEnglishLanguage : + $"{_sEnglishLanguage} ({_sEnglishCountry})"; // Native locale info - (culture._sNativeLanguage, culture._sNativeCountry) = culture.JSGetLocaleInfo(localeName, localeName); - culture._sNativeDisplayName = string.IsNullOrEmpty(culture._sNativeCountry) ? - culture._sNativeLanguage : - $"{culture._sNativeLanguage} ({culture._sNativeCountry})"; + (_sNativeLanguage, _sNativeCountry) = JSGetLocaleInfo(localeName, localeName); + _sNativeDisplayName = string.IsNullOrEmpty(_sNativeCountry) ? + _sNativeLanguage : + $"{_sNativeLanguage} ({_sNativeCountry})"; } - return culture; } private unsafe (string, string) JSGetLocaleInfo(string cultureName, string localeName) diff --git a/src/libraries/System.Private.CoreLib/src/System/Globalization/CultureData.cs b/src/libraries/System.Private.CoreLib/src/System/Globalization/CultureData.cs index 47f87734558a1..189ce06de7033 100644 --- a/src/libraries/System.Private.CoreLib/src/System/Globalization/CultureData.cs +++ b/src/libraries/System.Private.CoreLib/src/System/Globalization/CultureData.cs @@ -822,7 +822,7 @@ private static string NormalizeCultureName(string name, out bool isNeutralName) { culture = JSLoadCultureInfoFromBrowser(culture._sName, culture); } - culture = JSInitLocaleInfo(culture._sName, culture); + culture.JSInitLocaleInfo(); #endif // We need _sWindowsName to be initialized to know if we're using overrides.