Skip to content
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);
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)
{
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)
{
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