From 39e031c4ddc14f2aeecddf3dbf300603b491ab9f Mon Sep 17 00:00:00 2001 From: Timothy Flynn Date: Fri, 12 Nov 2021 13:11:30 -0500 Subject: [PATCH] LibJS+LibUnicode: Generate all styles of currency localizations Currently, LibUnicode is only parsing and generating the "long" style of currency display names. However, the CLDR contains "short" and "narrow" forms as well that need to be handled. Parse these, and update LibJS to actually respect the "style" option provided by the user for displaying currencies with Intl.DisplayNames. Note: There are some discrepencies between the engines on how style is handled. In particular, running: new Intl.DisplayNames('en', {type:'currency', style:'narrow'}).of('usd') Gives: SpiderMoney: "USD" V8: "US Dollar" LibJS: "$" And running: new Intl.DisplayNames('en', {type:'currency', style:'short'}).of('usd') Gives: SpiderMonkey: "$" V8: "US Dollar" LibJS: "$" My best guess is V8 isn't handling style, and just returning the long form (which is what LibJS did before this commit). And SpiderMoney can handle some styles, but if they don't have a value for the requested style, they fall back to the canonicalized code passed into of(). --- .../LibUnicode/GenerateUnicodeLocale.cpp | 45 +++++++++++++------ .../Runtime/Intl/DisplayNamesPrototype.cpp | 14 +++++- .../DisplayNames/DisplayNames.prototype.of.js | 38 ++++++++++++++-- Userland/Libraries/LibUnicode/Locale.cpp | 16 +++++-- Userland/Libraries/LibUnicode/Locale.h | 9 +++- 5 files changed, 98 insertions(+), 24 deletions(-) diff --git a/Meta/Lagom/Tools/CodeGenerators/LibUnicode/GenerateUnicodeLocale.cpp b/Meta/Lagom/Tools/CodeGenerators/LibUnicode/GenerateUnicodeLocale.cpp index 229d022b01..b8d2d1cb5f 100644 --- a/Meta/Lagom/Tools/CodeGenerators/LibUnicode/GenerateUnicodeLocale.cpp +++ b/Meta/Lagom/Tools/CodeGenerators/LibUnicode/GenerateUnicodeLocale.cpp @@ -40,7 +40,10 @@ struct Locale { HashMap languages; HashMap territories; HashMap scripts; - HashMap currencies; + HashMap long_currencies; + HashMap short_currencies; + HashMap narrow_currencies; + HashMap numeric_currencies; HashMap keywords; Vector list_patterns; }; @@ -335,10 +338,15 @@ static void parse_locale_currencies(String numbers_path, UnicodeLocaleData& loca auto const& currencies_object = locale_numbers_object.as_object().get("currencies"sv); currencies_object.as_object().for_each_member([&](auto const& key, JsonValue const& value) { - auto const& display_name = value.as_object().get("displayName"sv); + auto const& long_name = value.as_object().get("displayName"sv); + auto const& short_name = value.as_object().get("symbol"sv); + auto const& narrow_name = value.as_object().get("symbol-alt-narrow"sv); + auto const& numeric_name = value.as_object().get("displayName-count-other"sv); - auto index = locale_data.unique_strings.ensure(display_name.as_string()); - locale.currencies.set(key, index); + locale.long_currencies.set(key, locale_data.unique_strings.ensure(long_name.as_string())); + locale.short_currencies.set(key, locale_data.unique_strings.ensure(short_name.as_string())); + locale.narrow_currencies.set(key, narrow_name.is_null() ? 0 : locale_data.unique_strings.ensure(narrow_name.as_string())); + locale.numeric_currencies.set(key, locale_data.unique_strings.ensure(numeric_name.is_null() ? long_name.as_string() : numeric_name.as_string())); if (!locale_data.currencies.contains_slow(key)) locale_data.currencies.append(key); @@ -565,7 +573,10 @@ Optional get_locale_script_tag_mapping(StringView locale, StringView Optional script_tag_from_string(StringView script_tag); Optional resolve_script_tag_alias(StringView script_tag); -Optional get_locale_currency_mapping(StringView locale, StringView currency); +Optional get_locale_long_currency_mapping(StringView locale, StringView currency); +Optional get_locale_short_currency_mapping(StringView locale, StringView currency); +Optional get_locale_narrow_currency_mapping(StringView locale, StringView currency); +Optional get_locale_numeric_currency_mapping(StringView locale, StringView currency); Optional currency_from_string(StringView currency); Optional get_locale_key_mapping(StringView locale, StringView key); @@ -700,7 +711,10 @@ static constexpr Array @name@ { {)~~~"); generate_mapping(generator, locale_data.locales, s_string_index_type, "s_languages"sv, "s_languages_{}", [&](auto const& name, auto const& value) { append_string_index_list(name, locale_data.languages, value.languages); }); generate_mapping(generator, locale_data.locales, s_string_index_type, "s_territories"sv, "s_territories_{}", [&](auto const& name, auto const& value) { append_string_index_list(name, locale_data.territories, value.territories); }); generate_mapping(generator, locale_data.locales, s_string_index_type, "s_scripts"sv, "s_scripts_{}", [&](auto const& name, auto const& value) { append_string_index_list(name, locale_data.scripts, value.scripts); }); - generate_mapping(generator, locale_data.locales, s_string_index_type, "s_currencies"sv, "s_currencies_{}", [&](auto const& name, auto const& value) { append_string_index_list(name, locale_data.currencies, value.currencies); }); + generate_mapping(generator, locale_data.locales, s_string_index_type, "s_long_currencies"sv, "s_long_currencies_{}", [&](auto const& name, auto const& value) { append_string_index_list(name, locale_data.currencies, value.long_currencies); }); + generate_mapping(generator, locale_data.locales, s_string_index_type, "s_short_currencies"sv, "s_short_currencies_{}", [&](auto const& name, auto const& value) { append_string_index_list(name, locale_data.currencies, value.short_currencies); }); + generate_mapping(generator, locale_data.locales, s_string_index_type, "s_narrow_currencies"sv, "s_narrow_currencies_{}", [&](auto const& name, auto const& value) { append_string_index_list(name, locale_data.currencies, value.narrow_currencies); }); + generate_mapping(generator, locale_data.locales, s_string_index_type, "s_numeric_currencies"sv, "s_numeric_currencies_{}", [&](auto const& name, auto const& value) { append_string_index_list(name, locale_data.currencies, value.numeric_currencies); }); generate_mapping(generator, locale_data.locales, s_string_index_type, "s_keywords"sv, "s_keywords_{}", [&](auto const& name, auto const& value) { append_string_index_list(name, locale_data.keywords, value.keywords); }); generate_mapping(generator, locale_data.locales, "Patterns"sv, "s_list_patterns"sv, "s_list_patterns_{}", [&](auto const& name, auto const& value) { append_list_patterns(name, value.list_patterns); }); @@ -884,9 +898,9 @@ static LanguageMapping const* resolve_likely_subtag(Unicode::LanguageID const& l )~~~"); - auto append_mapping_search = [&](StringView enum_title, StringView enum_snake, StringView collection_name) { - generator.set("enum_title", enum_title); + auto append_mapping_search = [&](StringView enum_snake, StringView from_string_name, StringView collection_name) { generator.set("enum_snake", enum_snake); + generator.set("from_string_name", from_string_name); generator.set("collection_name", collection_name); generator.append(R"~~~( Optional get_locale_@enum_snake@_mapping(StringView locale, StringView @enum_snake@) @@ -895,7 +909,7 @@ Optional get_locale_@enum_snake@_mapping(StringView locale, StringVi if (!locale_value.has_value()) return {}; - auto @enum_snake@_value = @enum_snake@_from_string(@enum_snake@); + auto @enum_snake@_value = @from_string_name@_from_string(@enum_snake@); if (!@enum_snake@_value.has_value()) return {}; @@ -935,22 +949,25 @@ Optional get_locale_@enum_snake@_mapping(StringView locale, StringVi append_from_string("Locale"sv, "locale"sv, locale_data.locales.keys()); - append_mapping_search("Language"sv, "language"sv, "s_languages"sv); + append_mapping_search("language"sv, "language"sv, "s_languages"sv); append_from_string("Language"sv, "language"sv, locale_data.languages); append_alias_search("language"sv, locale_data.language_aliases); - append_mapping_search("Territory"sv, "territory"sv, "s_territories"sv); + append_mapping_search("territory"sv, "territory"sv, "s_territories"sv); append_from_string("Territory"sv, "territory"sv, locale_data.territories); append_alias_search("territory"sv, locale_data.territory_aliases); - append_mapping_search("ScriptTag"sv, "script_tag"sv, "s_scripts"sv); + append_mapping_search("script_tag"sv, "script_tag"sv, "s_scripts"sv); append_from_string("ScriptTag"sv, "script_tag"sv, locale_data.scripts); append_alias_search("script_tag"sv, locale_data.script_aliases); - append_mapping_search("Currency"sv, "currency"sv, "s_currencies"sv); + append_mapping_search("long_currency"sv, "currency"sv, "s_long_currencies"sv); + append_mapping_search("short_currency"sv, "currency"sv, "s_short_currencies"sv); + append_mapping_search("narrow_currency"sv, "currency"sv, "s_narrow_currencies"sv); + append_mapping_search("numeric_currency"sv, "currency"sv, "s_numeric_currencies"sv); append_from_string("Currency"sv, "currency"sv, locale_data.currencies); - append_mapping_search("Key"sv, "key"sv, "s_keywords"sv); + append_mapping_search("key"sv, "key"sv, "s_keywords"sv); append_from_string("Key"sv, "key"sv, locale_data.keywords); append_alias_search("variant"sv, locale_data.variant_aliases); diff --git a/Userland/Libraries/LibJS/Runtime/Intl/DisplayNamesPrototype.cpp b/Userland/Libraries/LibJS/Runtime/Intl/DisplayNamesPrototype.cpp index 5370b64670..e59954919c 100644 --- a/Userland/Libraries/LibJS/Runtime/Intl/DisplayNamesPrototype.cpp +++ b/Userland/Libraries/LibJS/Runtime/Intl/DisplayNamesPrototype.cpp @@ -63,7 +63,19 @@ JS_DEFINE_NATIVE_FUNCTION(DisplayNamesPrototype::of) result = Unicode::get_locale_script_mapping(display_names->locale(), code.as_string().string()); break; case DisplayNames::Type::Currency: - result = Unicode::get_locale_currency_mapping(display_names->locale(), code.as_string().string()); + switch (display_names->style()) { + case DisplayNames::Style::Long: + result = Unicode::get_locale_currency_mapping(display_names->locale(), code.as_string().string(), Unicode::Style::Long); + break; + case DisplayNames::Style::Short: + result = Unicode::get_locale_currency_mapping(display_names->locale(), code.as_string().string(), Unicode::Style::Short); + break; + case DisplayNames::Style::Narrow: + result = Unicode::get_locale_currency_mapping(display_names->locale(), code.as_string().string(), Unicode::Style::Narrow); + break; + default: + VERIFY_NOT_REACHED(); + } break; default: VERIFY_NOT_REACHED(); diff --git a/Userland/Libraries/LibJS/Tests/builtins/Intl/DisplayNames/DisplayNames.prototype.of.js b/Userland/Libraries/LibJS/Tests/builtins/Intl/DisplayNames/DisplayNames.prototype.of.js index 7cee5637c8..3f85173188 100644 --- a/Userland/Libraries/LibJS/Tests/builtins/Intl/DisplayNames/DisplayNames.prototype.of.js +++ b/Userland/Libraries/LibJS/Tests/builtins/Intl/DisplayNames/DisplayNames.prototype.of.js @@ -74,18 +74,48 @@ describe("correct behavior", () => { expect(zhHant.of("Aaaa")).toBe("Aaaa"); }); - test("option type currency", () => { - const en = new Intl.DisplayNames("en", { type: "currency" }); + test("option type currency, style long", () => { + const en = new Intl.DisplayNames("en", { type: "currency", style: "long" }); expect(en.of("USD")).toBe("US Dollar"); - const es419 = new Intl.DisplayNames("es-419", { type: "currency" }); + const es419 = new Intl.DisplayNames("es-419", { type: "currency", style: "long" }); expect(es419.of("USD")).toBe("dólar estadounidense"); - const zhHant = new Intl.DisplayNames(["zh-Hant"], { type: "currency" }); + const zhHant = new Intl.DisplayNames(["zh-Hant"], { type: "currency", style: "long" }); expect(zhHant.of("USD")).toBe("美元"); expect(en.of("AAA")).toBe("AAA"); expect(es419.of("AAA")).toBe("AAA"); expect(zhHant.of("AAA")).toBe("AAA"); }); + + test("option type currency, style short", () => { + const en = new Intl.DisplayNames("en", { type: "currency", style: "short" }); + expect(en.of("USD")).toBe("$"); + + const es419 = new Intl.DisplayNames("es-419", { type: "currency", style: "short" }); + expect(es419.of("USD")).toBe("USD"); + + const zhHant = new Intl.DisplayNames(["zh-Hant"], { type: "currency", style: "short" }); + expect(zhHant.of("USD")).toBe("US$"); + + expect(en.of("AAA")).toBe("AAA"); + expect(es419.of("AAA")).toBe("AAA"); + expect(zhHant.of("AAA")).toBe("AAA"); + }); + + test("option type currency, style narrow", () => { + const en = new Intl.DisplayNames("en", { type: "currency", style: "narrow" }); + expect(en.of("USD")).toBe("$"); + + const es419 = new Intl.DisplayNames("es-419", { type: "currency", style: "narrow" }); + expect(es419.of("USD")).toBe("$"); + + const zhHant = new Intl.DisplayNames(["zh-Hant"], { type: "currency", style: "narrow" }); + expect(zhHant.of("USD")).toBe("$"); + + expect(en.of("AAA")).toBe("AAA"); + expect(es419.of("AAA")).toBe("AAA"); + expect(zhHant.of("AAA")).toBe("AAA"); + }); }); diff --git a/Userland/Libraries/LibUnicode/Locale.cpp b/Userland/Libraries/LibUnicode/Locale.cpp index 60cc6c7ec4..92cbd88f88 100644 --- a/Userland/Libraries/LibUnicode/Locale.cpp +++ b/Userland/Libraries/LibUnicode/Locale.cpp @@ -786,13 +786,21 @@ Optional get_locale_script_mapping([[maybe_unused]] StringView local #endif } -Optional get_locale_currency_mapping([[maybe_unused]] StringView locale, [[maybe_unused]] StringView currency) +Optional get_locale_currency_mapping([[maybe_unused]] StringView locale, [[maybe_unused]] StringView currency, [[maybe_unused]] Style style) { #if ENABLE_UNICODE_DATA - return Detail::get_locale_currency_mapping(locale, currency); -#else - return {}; + switch (style) { + case Style::Long: + return Detail::get_locale_long_currency_mapping(locale, currency); + case Style::Short: + return Detail::get_locale_short_currency_mapping(locale, currency); + case Style::Narrow: + return Detail::get_locale_narrow_currency_mapping(locale, currency); + case Style::Numeric: + return Detail::get_locale_numeric_currency_mapping(locale, currency); + } #endif + return {}; } Vector get_locale_key_mapping([[maybe_unused]] StringView locale, [[maybe_unused]] StringView keyword) diff --git a/Userland/Libraries/LibUnicode/Locale.h b/Userland/Libraries/LibUnicode/Locale.h index 3c53705a1a..504bec6dbd 100644 --- a/Userland/Libraries/LibUnicode/Locale.h +++ b/Userland/Libraries/LibUnicode/Locale.h @@ -78,6 +78,13 @@ struct LocaleID { Vector private_use_extensions {}; }; +enum class Style : u8 { + Long, + Short, + Narrow, + Numeric, +}; + enum class StandardNumberFormatType : u8 { Decimal, Currency, @@ -170,7 +177,7 @@ Optional locale_from_string(StringView locale); Optional get_locale_language_mapping(StringView locale, StringView language); Optional get_locale_territory_mapping(StringView locale, StringView territory); Optional get_locale_script_mapping(StringView locale, StringView script); -Optional get_locale_currency_mapping(StringView locale, StringView currency); +Optional get_locale_currency_mapping(StringView locale, StringView currency, Style style); Vector get_locale_key_mapping(StringView locale, StringView keyword); Optional get_number_system_symbol(StringView locale, StringView system, StringView symbol); Optional get_standard_number_system_format(StringView locale, StringView system, StandardNumberFormatType type);