Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[browser] Revert to full NativeName by interop with JS #99956

Merged
merged 18 commits into from
Mar 26, 2024
Merged
Show file tree
Hide file tree
Changes from 2 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions src/libraries/Common/src/Interop/Browser/Interop.Locale.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -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);
ilonatommy marked this conversation as resolved.
Show resolved Hide resolved
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];
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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)
ilonatommy marked this conversation as resolved.
Show resolved Hide resolved
{
return JSGetNativeName(_sWindowsName, uiCultureName);
}
#endif
return IcuGetLocaleInfo(_sWindowsName, type, uiCultureName);
}

Expand Down Expand Up @@ -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;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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<object[]> SupportedCultures_TestData()
{
// Browser does not support all ICU locales but it uses JS to get the correct native name
if (!PlatformDetection.IsBrowser)
ilonatommy marked this conversation as resolved.
Show resolved Hide resolved
{
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)))
Expand Down
2 changes: 2 additions & 0 deletions src/mono/browser/runtime/corebindings.c
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand Down Expand Up @@ -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);
Expand Down
3 changes: 2 additions & 1 deletion src/mono/browser/runtime/exports-binding.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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";

Expand Down Expand Up @@ -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,
];
Expand Down
38 changes: 37 additions & 1 deletion src/mono/browser/runtime/hybrid-globalization/locales.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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<MonoString>(locale),
cultureRoot = mono_wasm_new_external_root<MonoString>(culture),
exceptionRoot = mono_wasm_new_external_root<MonoObject>(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<MonoString>(culture),
Expand Down
Loading