ladybird/Libraries/LibJS/Runtime/Intl/DisplayNames.cpp
Andreas Kling b6bef6b688 Libraries: Use UTF-16 for JS-visible runtime strings
Produce JS-visible string results as UTF-16 at their source, including
numeric formatting, BigInt and BigFraction formatting, URI encoding,
console formatting, parser errors, regular expression errors, Intl and
Temporal records, LibUnicode locale boundaries, and LibWeb bindings.

Handle fractional radix formatting through the UTF-16 builder view.
2026-06-22 19:51:25 +02:00

182 lines
6.8 KiB
C++

/*
* Copyright (c) 2021-2025, Tim Flynn <trflynn89@ladybird.org>
*
* SPDX-License-Identifier: BSD-2-Clause
*/
#include <LibJS/Runtime/Intl/AbstractOperations.h>
#include <LibJS/Runtime/Intl/DisplayNames.h>
#include <LibJS/Runtime/VM.h>
namespace JS::Intl {
GC_DEFINE_ALLOCATOR(DisplayNames);
// 12 DisplayNames Objects, https://tc39.es/ecma402/#intl-displaynames-objects
DisplayNames::DisplayNames(Object& prototype)
: IntlObject(ConstructWithPrototypeTag::Tag, prototype)
{
}
// 12.2.3 Internal slots, https://tc39.es/ecma402/#sec-Intl.DisplayNames-internal-slots
ReadonlySpan<Utf16View> DisplayNames::relevant_extension_keys() const
{
// The value of the [[RelevantExtensionKeys]] internal slot is « ».
return {};
}
// 12.2.3 Internal slots, https://tc39.es/ecma402/#sec-Intl.DisplayNames-internal-slots
ReadonlySpan<ResolutionOptionDescriptor> DisplayNames::resolution_option_descriptors(VM&) const
{
// The value of the [[ResolutionOptionDescriptors]] internal slot is « ».
return {};
}
void DisplayNames::set_type(Utf16View type)
{
if (type == "language"sv)
m_type = Type::Language;
else if (type == "region"sv)
m_type = Type::Region;
else if (type == "script"sv)
m_type = Type::Script;
else if (type == "currency"sv)
m_type = Type::Currency;
else if (type == "calendar"sv)
m_type = Type::Calendar;
else if (type == "dateTimeField"sv)
m_type = Type::DateTimeField;
else
VERIFY_NOT_REACHED();
}
Utf16String DisplayNames::type_string() const
{
switch (m_type) {
case Type::Language:
return "language"_utf16;
case Type::Region:
return "region"_utf16;
case Type::Script:
return "script"_utf16;
case Type::Currency:
return "currency"_utf16;
case Type::Calendar:
return "calendar"_utf16;
case Type::DateTimeField:
return "dateTimeField"_utf16;
default:
VERIFY_NOT_REACHED();
}
}
void DisplayNames::set_fallback(Utf16View fallback)
{
if (fallback == "none"sv)
m_fallback = Fallback::None;
else if (fallback == "code"sv)
m_fallback = Fallback::Code;
else
VERIFY_NOT_REACHED();
}
Utf16String DisplayNames::fallback_string() const
{
switch (m_fallback) {
case Fallback::None:
return "none"_utf16;
case Fallback::Code:
return "code"_utf16;
default:
VERIFY_NOT_REACHED();
}
}
// 12.5.1 CanonicalCodeForDisplayNames ( type, code ), https://tc39.es/ecma402/#sec-canonicalcodefordisplaynames
ThrowCompletionOr<Value> canonical_code_for_display_names(VM& vm, DisplayNames::Type type, Utf16View code)
{
// 1. If type is "language", then
if (type == DisplayNames::Type::Language) {
// a. If code does not match the unicode_language_id production, throw a RangeError exception.
if (!Unicode::parse_unicode_language_id(code).has_value())
return vm.throw_completion<RangeError>(ErrorType::OptionIsNotValidValue, code, "language"sv);
// b. If IsWellFormedLanguageTag(code) is false, throw a RangeError exception.
if (!is_well_formed_language_tag(code))
return vm.throw_completion<RangeError>(ErrorType::IntlInvalidLanguageTag, code);
// c. Return ! CanonicalizeUnicodeLocaleId(code).
auto canonicalized_tag = canonicalize_unicode_locale_id(code);
return PrimitiveString::create(vm, canonicalized_tag);
}
// 2. If type is "region", then
if (type == DisplayNames::Type::Region) {
// a. If code does not match the unicode_region_subtag production, throw a RangeError exception.
if (!Unicode::is_unicode_region_subtag(code))
return vm.throw_completion<RangeError>(ErrorType::OptionIsNotValidValue, code, "region"sv);
// b. Return the ASCII-uppercase of code.
return PrimitiveString::create(vm, code.to_ascii_uppercase());
}
// 3. If type is "script", then
if (type == DisplayNames::Type::Script) {
// a. If code does not match the unicode_script_subtag production, throw a RangeError exception.
if (!Unicode::is_unicode_script_subtag(code))
return vm.throw_completion<RangeError>(ErrorType::OptionIsNotValidValue, code, "script"sv);
// Assert: The length of code is 4, and every code unit of code represents an ASCII letter (0x0041 through 0x005A and 0x0061 through 0x007A, both inclusive).
VERIFY(code.length_in_code_units() == 4);
VERIFY(all_of(code, is_ascii_alpha));
// c. Let first be the ASCII-uppercase of the substring of code from 0 to 1.
// d. Let rest be the ASCII-lowercase of the substring of code from 1.
// e. Return the string-concatenation of first and rest.
return PrimitiveString::create(vm, code.to_ascii_titlecase());
}
// 4. If type is "calendar", then
if (type == DisplayNames::Type::Calendar) {
// a. If code does not match the Unicode Locale Identifier type nonterminal, throw a RangeError exception.
if (!Unicode::is_type_identifier(code))
return vm.throw_completion<RangeError>(ErrorType::OptionIsNotValidValue, code, "calendar"sv);
// b. If code uses any of the backwards compatibility syntax described in Unicode Technical Standard #35 LDML § 3.3 BCP 47 Conformance, throw a RangeError exception.
if (code.contains(u"_"sv))
return vm.throw_completion<RangeError>(ErrorType::OptionIsNotValidValue, code, "calendar"sv);
// c. Return the ASCII-lowercase of code.
return PrimitiveString::create(vm, code.to_ascii_lowercase());
}
// 5. If type is "dateTimeField", then
if (type == DisplayNames::Type::DateTimeField) {
// a. If the result of IsValidDateTimeFieldCode(code) is false, throw a RangeError exception.
if (!is_valid_date_time_field_code(code))
return vm.throw_completion<RangeError>(ErrorType::OptionIsNotValidValue, code, "dateTimeField"sv);
// b. Return code.
return PrimitiveString::create(vm, Utf16String::from_utf16(code));
}
// 6. Assert: type is "currency".
VERIFY(type == DisplayNames::Type::Currency);
// 7. If ! IsWellFormedCurrencyCode(code) is false, throw a RangeError exception.
if (!is_well_formed_currency_code(code))
return vm.throw_completion<RangeError>(ErrorType::OptionIsNotValidValue, code, "currency"sv);
// 8. Return the ASCII-uppercase of code.
return PrimitiveString::create(vm, code.to_ascii_uppercase());
}
// 12.5.2 IsValidDateTimeFieldCode ( field ), https://tc39.es/ecma402/#sec-isvaliddatetimefieldcode
bool is_valid_date_time_field_code(Utf16View field)
{
// 1. If field is listed in the Code column of Table 19, return true.
// 2. Return false.
return field.is_one_of("era"sv, "year"sv, "quarter"sv, "month"sv, "weekOfYear"sv, "weekday"sv, "day"sv, "dayPeriod"sv, "hour"sv, "minute"sv, "second"sv, "timeZoneName"sv);
}
}