diff options
Diffstat (limited to 'intl/components/src/DisplayNames.h')
-rw-r--r-- | intl/components/src/DisplayNames.h | 971 |
1 files changed, 971 insertions, 0 deletions
diff --git a/intl/components/src/DisplayNames.h b/intl/components/src/DisplayNames.h new file mode 100644 index 0000000000..ae519f61ce --- /dev/null +++ b/intl/components/src/DisplayNames.h @@ -0,0 +1,971 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ +#ifndef intl_components_DisplayNames_h_ +#define intl_components_DisplayNames_h_ + +#include <string> +#include <string_view> +#include "unicode/udat.h" +#include "unicode/udatpg.h" +#include "unicode/uldnames.h" +#include "unicode/uloc.h" +#include "unicode/ucurr.h" +#include "mozilla/intl/Calendar.h" +#include "mozilla/intl/DateTimePatternGenerator.h" +#include "mozilla/intl/ICU4CGlue.h" +#include "mozilla/intl/Locale.h" +#include "mozilla/Buffer.h" +#include "mozilla/Casting.h" +#include "mozilla/PodOperations.h" +#include "mozilla/Result.h" +#include "mozilla/Span.h" +#include "mozilla/TextUtils.h" +#include "mozilla/UniquePtr.h" + +namespace mozilla::intl { +/** + * Provide more granular errors for DisplayNames rather than use the generic + * ICUError type. This helps with providing more actionable feedback for + * errors with input validation. + * + * This type can't be nested in the DisplayNames class because it needs the + * UnusedZero and HasFreeLSB definitions. + */ +enum class DisplayNamesError { + // Since we claim UnusedZero<DisplayNamesError>::value and + // HasFreeLSB<Error>::value == true below, we must only use positive, + // even enum values. + InternalError = 2, + OutOfMemory = 4, + InvalidOption = 6, + DuplicateVariantSubtag = 8, + InvalidLanguageTag = 10, +}; +} // namespace mozilla::intl + +namespace mozilla::detail { +// Ensure the efficient packing of the error types into the result. See +// ICUError.h and the ICUError comments for more information. +template <> +struct UnusedZero<intl::DisplayNamesError> + : UnusedZeroEnum<intl::DisplayNamesError> {}; + +template <> +struct HasFreeLSB<intl::DisplayNamesError> { + static constexpr bool value = true; +}; +} // namespace mozilla::detail + +namespace mozilla::intl { + +// NOTE: The UTF-35 canonical "code" value for months and quarters are 1-based +// integers, so some of the following enums are 1-based for consistency with +// that. For simplicity, we make all of the following enums 1-based, but use +// `EnumToIndex` (see below) to convert to zero based if indexing into internal +// (non-ICU) tables. + +/** + * Month choices for display names. + */ +enum class Month : uint8_t { + January = 1, + February, + March, + April, + May, + June, + July, + August, + September, + October, + November, + December, + // Some calendar systems feature a 13th month. + // https://en.wikipedia.org/wiki/Undecimber + Undecimber +}; + +/** + * Quarter choices for display names. + */ +enum class Quarter : uint8_t { + Q1 = 1, + Q2, + Q3, + Q4, +}; + +/** + * Day period choices for display names. + */ +enum class DayPeriod : uint8_t { + AM = 1, + PM, +}; + +/** + * DateTimeField choices for display names. + */ +enum class DateTimeField : uint8_t { + Era = 1, + Year, + Quarter, + Month, + WeekOfYear, + Weekday, + Day, + DayPeriod, + Hour, + Minute, + Second, + TimeZoneName, +}; + +/** + * DisplayNames provide a way to get the localized names of various types of + * information such as the names of the day of the week, months, currency etc. + * + * This class backs SpiderMonkeys implementation of Intl.DisplayNames + * https://tc39.es/ecma402/#intl-displaynames-objects + */ +class DisplayNames final { + public: + /** + * The style of the display name, specified by the amount of space available + * for displaying the text. + */ + enum class Style { + Narrow, + Short, + Long, + // Note: Abbreviated is not part of ECMA-402, but it is available for + // internal Mozilla usage. + Abbreviated, + }; + + /** + * Use either standard or dialect names for the "Language" type. + */ + enum class LanguageDisplay { + Standard, + Dialect, + }; + + /** + * Determines the fallback behavior if no match is found. + */ + enum class Fallback { + // The buffer will contain an empty string. + None, + // The buffer will contain the code, but typically in a canonicalized form. + Code + }; + + /** + * These options correlate to the ECMA-402 DisplayNames options. The defaults + * values must match the default initialized values of ECMA-402. The type + * option is omitted as the C++ API relies on directly calling the + * DisplayNames::Get* methods. + * + * https://tc39.es/ecma402/#intl-displaynames-objects + * https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Intl/DisplayNames + */ + struct Options { + Style style = Style::Long; + LanguageDisplay languageDisplay = LanguageDisplay::Standard; + }; + + DisplayNames(ULocaleDisplayNames* aDisplayNames, Span<const char> aLocale, + Options aOptions) + : mOptions(aOptions), mULocaleDisplayNames(aDisplayNames) { + MOZ_ASSERT(aDisplayNames); + + // Copy the span and ensure null termination. + mLocale = Buffer<char>(aLocale.size() + 1); + PodCopy(mLocale.begin(), aLocale.data(), aLocale.size()); + mLocale[aLocale.size()] = '\0'; + } + + /** + * Initialize a new DisplayNames for the provided locale and using the + * provided options. + * + * https://tc39.es/ecma402/#sec-Intl.DisplayNames + */ + static Result<UniquePtr<DisplayNames>, ICUError> TryCreate( + const char* aLocale, Options aOptions); + + // Not copyable or movable + DisplayNames(const DisplayNames&) = delete; + DisplayNames& operator=(const DisplayNames&) = delete; + + ~DisplayNames(); + + /** + * Easily convert to a more specific DisplayNames error. + */ + DisplayNamesError ToError(ICUError aError) const; + + /** + * Easily convert to a more specific DisplayNames error. + */ + DisplayNamesError ToError(Locale::CanonicalizationError aError) const; + + private: + /** + * A helper function to handle the fallback behavior, where if there is a + * fallback the buffer is filled with the "code", often in canonicalized form. + */ + template <typename B, typename Fn> + static Result<Ok, DisplayNamesError> HandleFallback(B& aBuffer, + Fallback aFallback, + Fn aGetFallbackSpan) { + if (aBuffer.length() == 0 && + aFallback == mozilla::intl::DisplayNames::Fallback::Code) { + if (!FillBuffer(aGetFallbackSpan(), aBuffer)) { + return Err(DisplayNamesError::OutOfMemory); + } + } + return Ok(); + } + + /** + * This is a specialized form of the FillBufferWithICUCall for DisplayNames. + * Different APIs report that no display name is found with different + * statuses. This method signals no display name was found by setting the + * buffer to 0. + * + * The display name APIs such as `uldn_scriptDisplayName`, + * `uloc_getDisplayScript`, and `uldn_regionDisplayName` report + * U_ILLEGAL_ARGUMENT_ERROR when no display name was found. In order to + * accomodate fallbacking, return an empty string in this case. + */ + template <typename B, typename F> + static ICUResult FillBufferWithICUDisplayNames( + B& aBuffer, UErrorCode aNoDisplayNameStatus, F aCallback) { + return FillBufferWithICUCall( + aBuffer, [&](UChar* target, int32_t length, UErrorCode* status) { + int32_t res = aCallback(target, length, status); + + if (*status == aNoDisplayNameStatus) { + *status = U_ZERO_ERROR; + res = 0; + } + return res; + }); + } + + /** + * An internal helper to compute the list of display names for various + * DateTime options. + */ + Result<Ok, DisplayNamesError> ComputeDateTimeDisplayNames( + UDateFormatSymbolType symbolType, mozilla::Span<const int32_t> indices, + Span<const char> aCalendar); + + // The following are the stack-allocated sizes for various strings using the + // mozilla::Vector. The numbers should be large enough to fit the common + // cases, and when the strings are too large they will fall back to heap + // allocations. + + // Fit BCP 47 locales such as "en-US", "zh-Hant". Locales can get quite long, + // but 32 should fit most smaller locales without a lot of extensions. + static constexpr size_t LocaleVecLength = 32; + // Fit calendar names such as "gregory", "buddhist", "islamic-civil". + // "islamic-umalqura" is 16 bytes + 1 for null termination, so round up to 32. + static constexpr size_t CalendarVecLength = 32; + + /** + * Given an ASCII alpha, convert it to upper case. + */ + static inline char16_t AsciiAlphaToUpperCase(char16_t aCh) { + MOZ_ASSERT(IsAsciiAlpha(aCh)); + return AsciiToUpperCase(aCh); + }; + + /** + * Attempt to use enums to safely index into an array. + * + * Note: The enums we support here are all defined starting from 1. + */ + template <typename T> + inline int32_t EnumToIndex(size_t aSize, T aEnum) { + size_t index = static_cast<size_t>(aEnum) - 1; + MOZ_RELEASE_ASSERT(index < aSize, + "Enum indexing mismatch for display names."); + return index; + } + + /** + * Convert the month to a numeric code as a string. + */ + static Span<const char> ToCodeString(Month aMonth); + + public: + /** + * Get the localized name of a language. Part of ECMA-402. + * + * Accepts: + * languageCode ["-" scriptCode] ["-" regionCode ] *("-" variant ) + * Where the language code is: + * 1. A two letters ISO 639-1 language code + * https://en.wikipedia.org/wiki/List_of_ISO_639-1_codes + * 2. A three letters ISO 639-2 language code + * https://en.wikipedia.org/wiki/List_of_ISO_639-2_codes + * + * Examples: + * "es-ES" => "European Spanish" (en-US), "español de España" (es-ES) + * "zh-Hant" => "Traditional Chinese" (en-US), "chino tradicional" (es-ES) + */ + template <typename B> + Result<Ok, DisplayNamesError> GetLanguage( + B& aBuffer, Span<const char> aLanguage, + Fallback aFallback = Fallback::None) const { + static_assert(std::is_same<typename B::CharType, char16_t>::value); + mozilla::intl::Locale tag; + if (LocaleParser::TryParseBaseName(aLanguage, tag).isErr()) { + return Err(DisplayNamesError::InvalidOption); + } + + { + // ICU always canonicalizes the input locale, but since we know that ICU's + // canonicalization is incomplete, we need to perform our own + // canonicalization to ensure consistent result. + auto result = tag.CanonicalizeBaseName(); + if (result.isErr()) { + return Err(ToError(result.unwrapErr())); + } + } + + Vector<char, DisplayNames::LocaleVecLength> tagVec; + { + VectorToBufferAdaptor tagBuffer(tagVec); + auto result = tag.ToString(tagBuffer); + if (result.isErr()) { + return Err(ToError(result.unwrapErr())); + } + if (!tagVec.append('\0')) { + // The tag should be null terminated. + return Err(DisplayNamesError::OutOfMemory); + } + } + + auto result = FillBufferWithICUDisplayNames( + aBuffer, U_ILLEGAL_ARGUMENT_ERROR, + [&](UChar* target, int32_t length, UErrorCode* status) { + return uldn_localeDisplayName(mULocaleDisplayNames.GetConst(), + tagVec.begin(), target, length, status); + }); + if (result.isErr()) { + return Err(ToError(result.unwrapErr())); + } + + return HandleFallback(aBuffer, aFallback, [&] { + // Remove the null terminator. + return Span(tagVec.begin(), tagVec.length() - 1); + }); + }; + + /** + * Get the localized name of a region. Part of ECMA-402. + * + * Accepts: + * 1. an ISO-3166 two letters: + * https://www.iso.org/iso-3166-country-codes.html + * 2. region code, or a three digits UN M49 Geographic Regions. + * https://unstats.un.org/unsd/methodology/m49/ + * + * Examples + * "US" => "United States" (en-US), "Estados Unidos", (es-ES) + * "158" => "Taiwan" (en-US), "Taiwán", (es-ES) + */ + template <typename B> + Result<Ok, DisplayNamesError> GetRegion( + B& aBuffer, Span<const char> aCode, + Fallback aFallback = Fallback::None) const { + static_assert(std::is_same<typename B::CharType, char16_t>::value); + + mozilla::intl::RegionSubtag region; + if (!IsStructurallyValidRegionTag(aCode)) { + return Err(DisplayNamesError::InvalidOption); + } + region.Set(aCode); + + mozilla::intl::Locale tag; + tag.SetLanguage("und"); + tag.SetRegion(region); + + { + // ICU always canonicalizes the input locale, but since we know that ICU's + // canonicalization is incomplete, we need to perform our own + // canonicalization to ensure consistent result. + auto result = tag.CanonicalizeBaseName(); + if (result.isErr()) { + return Err(ToError(result.unwrapErr())); + } + } + + MOZ_ASSERT(tag.Region().Present()); + + // Note: ICU requires the region subtag to be in canonical case. + const mozilla::intl::RegionSubtag& canonicalRegion = tag.Region(); + + char regionChars[mozilla::intl::LanguageTagLimits::RegionLength + 1] = {}; + std::copy_n(canonicalRegion.Span().data(), canonicalRegion.Length(), + regionChars); + + auto result = FillBufferWithICUDisplayNames( + aBuffer, U_ILLEGAL_ARGUMENT_ERROR, + [&](UChar* chars, uint32_t size, UErrorCode* status) { + return uldn_regionDisplayName( + mULocaleDisplayNames.GetConst(), regionChars, chars, + AssertedCast<int32_t, uint32_t>(size), status); + }); + + if (result.isErr()) { + return Err(ToError(result.unwrapErr())); + } + + return HandleFallback(aBuffer, aFallback, [&] { + region.ToUpperCase(); + return region.Span(); + }); + } + + /** + * Get the localized name of a currency. Part of ECMA-402. + * + * Accepts: + * A 3-letter ISO 4217 currency code. + * https://en.wikipedia.org/wiki/ISO_4217 + * + * Examples: + * "EUR" => "Euro" (en-US), "euro" (es_ES), "欧元", (zh) + * "JPY" => "Japanese Yen" (en-US), "yen" (es_ES), "日元", (zh) + */ + template <typename B> + Result<Ok, DisplayNamesError> GetCurrency( + B& aBuffer, Span<const char> aCurrency, + Fallback aFallback = Fallback::None) const { + static_assert(std::is_same<typename B::CharType, char16_t>::value); + if (aCurrency.size() != 3) { + return Err(DisplayNamesError::InvalidOption); + } + + if (!mozilla::IsAsciiAlpha(aCurrency[0]) || + !mozilla::IsAsciiAlpha(aCurrency[1]) || + !mozilla::IsAsciiAlpha(aCurrency[2])) { + return Err(DisplayNamesError::InvalidOption); + } + + // Normally this type of operation wouldn't be safe, but ASCII characters + // all take 1 byte in UTF-8 encoding, and can be zero padded to be valid + // UTF-16. Currency codes are all three ASCII letters. + char16_t currency[] = {static_cast<char16_t>(aCurrency[0]), + static_cast<char16_t>(aCurrency[1]), + static_cast<char16_t>(aCurrency[2]), u'\0'}; + + UCurrNameStyle style; + switch (mOptions.style) { + case Style::Long: + style = UCURR_LONG_NAME; + break; + case Style::Abbreviated: + case Style::Short: + style = UCURR_SYMBOL_NAME; + break; + case Style::Narrow: + style = UCURR_NARROW_SYMBOL_NAME; + break; + } + + int32_t length = 0; + UErrorCode status = U_ZERO_ERROR; + const char16_t* name = ucurr_getName(currency, IcuLocale(mLocale), style, + nullptr, &length, &status); + if (U_FAILURE(status)) { + return Err(DisplayNamesError::InternalError); + } + + if (status == U_USING_DEFAULT_WARNING) { + // A resource bundle lookup returned a result from the root locale. + if (aFallback == DisplayNames::Fallback::Code) { + // Return the canonicalized input when no localized currency name was + // found. Canonical case for currency is upper case. + if (!aBuffer.reserve(3)) { + return Err(DisplayNamesError::OutOfMemory); + } + aBuffer.data()[0] = AsciiAlphaToUpperCase(currency[0]); + aBuffer.data()[1] = AsciiAlphaToUpperCase(currency[1]); + aBuffer.data()[2] = AsciiAlphaToUpperCase(currency[2]); + aBuffer.written(3); + } else if (aBuffer.length() != 0) { + // Ensure an empty string is in the buffer when there is no fallback. + aBuffer.written(0); + } + return Ok(); + } + + if (!FillBuffer(Span(name, length), aBuffer)) { + return Err(DisplayNamesError::OutOfMemory); + } + + return Ok(); + } + + /** + * Get the localized name of a script. Part of ECMA-402. + * + * Accepts: + * ECMA-402 expects the ISO-15924 four letters script code. + * https://unicode.org/iso15924/iso15924-codes.html + * e.g. "Latn" + * + * Examples: + * "Cher" => "Cherokee" (en-US), "cherokee" (es-ES) + * "Latn" => "Latin" (en-US), "latino" (es-ES) + */ + template <typename B> + Result<Ok, DisplayNamesError> GetScript( + B& aBuffer, Span<const char> aScript, + Fallback aFallback = Fallback::None) const { + static_assert(std::is_same<typename B::CharType, char16_t>::value); + mozilla::intl::ScriptSubtag script; + if (!IsStructurallyValidScriptTag(aScript)) { + return Err(DisplayNamesError::InvalidOption); + } + script.Set(aScript); + + mozilla::intl::Locale tag; + tag.SetLanguage("und"); + + tag.SetScript(script); + + { + // ICU always canonicalizes the input locale, but since we know that ICU's + // canonicalization is incomplete, we need to perform our own + // canonicalization to ensure consistent result. + auto result = tag.CanonicalizeBaseName(); + if (result.isErr()) { + return Err(ToError(result.unwrapErr())); + } + } + + MOZ_ASSERT(tag.Script().Present()); + mozilla::Vector<char, DisplayNames::LocaleVecLength> tagString; + VectorToBufferAdaptor buffer(tagString); + + switch (mOptions.style) { + case Style::Long: { + // |uldn_scriptDisplayName| doesn't use the stand-alone form for script + // subtags, so we're using |uloc_getDisplayScript| instead. (This only + // applies to the long form.) + // + // ICU bug: https://unicode-org.atlassian.net/browse/ICU-9301 + + // |uloc_getDisplayScript| expects a full locale identifier as its + // input. + if (auto result = tag.ToString(buffer); result.isErr()) { + return Err(ToError(result.unwrapErr())); + } + + // Null terminate the tag string. + if (!tagString.append('\0')) { + return Err(DisplayNamesError::OutOfMemory); + } + + auto result = FillBufferWithICUDisplayNames( + aBuffer, U_USING_DEFAULT_WARNING, + [&](UChar* target, int32_t length, UErrorCode* status) { + return uloc_getDisplayScript(tagString.begin(), + IcuLocale(mLocale), target, length, + status); + }); + + if (result.isErr()) { + return Err(ToError(result.unwrapErr())); + } + break; + } + case Style::Abbreviated: + case Style::Short: + case Style::Narrow: { + // Note: ICU requires the script subtag to be in canonical case. + const mozilla::intl::ScriptSubtag& canonicalScript = tag.Script(); + + char scriptChars[mozilla::intl::LanguageTagLimits::ScriptLength + 1] = + {}; + MOZ_ASSERT(canonicalScript.Length() <= + mozilla::intl::LanguageTagLimits::ScriptLength + 1); + std::copy_n(canonicalScript.Span().data(), canonicalScript.Length(), + scriptChars); + + auto result = FillBufferWithICUDisplayNames( + aBuffer, U_ILLEGAL_ARGUMENT_ERROR, + [&](UChar* target, int32_t length, UErrorCode* status) { + return uldn_scriptDisplayName(mULocaleDisplayNames.GetConst(), + scriptChars, target, length, + status); + }); + + if (result.isErr()) { + return Err(ToError(result.unwrapErr())); + } + break; + } + } + + return HandleFallback(aBuffer, aFallback, [&] { + script.ToTitleCase(); + return script.Span(); + }); + }; + + /** + * Get the localized name of a calendar. + * Part of Intl.DisplayNames V2. https://tc39.es/intl-displaynames-v2/ + * Accepts: + * Unicode calendar key: + * https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Intl/Locale/calendar#unicode_calendar_keys + */ + template <typename B> + Result<Ok, DisplayNamesError> GetCalendar( + B& aBuffer, Span<const char> aCalendar, + Fallback aFallback = Fallback::None) const { + if (aCalendar.empty() || !IsAscii(aCalendar)) { + return Err(DisplayNamesError::InvalidOption); + } + + if (LocaleParser::CanParseUnicodeExtensionType(aCalendar).isErr()) { + return Err(DisplayNamesError::InvalidOption); + } + + // Convert into canonical case before searching for replacements. + Vector<char, DisplayNames::CalendarVecLength> lowerCaseCalendar; + for (size_t i = 0; i < aCalendar.size(); i++) { + if (!lowerCaseCalendar.append(AsciiToLowerCase(aCalendar[i]))) { + return Err(DisplayNamesError::OutOfMemory); + } + } + if (!lowerCaseCalendar.append('\0')) { + return Err(DisplayNamesError::OutOfMemory); + } + + Span<const char> canonicalCalendar = mozilla::Span( + lowerCaseCalendar.begin(), lowerCaseCalendar.length() - 1); + + // Search if there's a replacement for the Unicode calendar keyword. + { + Span<const char> key = mozilla::MakeStringSpan("ca"); + Span<const char> type = canonicalCalendar; + if (const char* replacement = + mozilla::intl::Locale::ReplaceUnicodeExtensionType(key, type)) { + canonicalCalendar = MakeStringSpan(replacement); + } + } + + // The input calendar name is user-controlled, so be extra cautious before + // passing arbitrarily large strings to ICU. + static constexpr size_t maximumCalendarLength = 100; + + if (canonicalCalendar.size() <= maximumCalendarLength) { + // |uldn_keyValueDisplayName| expects old-style keyword values. + if (const char* legacyCalendar = + uloc_toLegacyType("calendar", canonicalCalendar.Elements())) { + auto result = FillBufferWithICUDisplayNames( + aBuffer, U_ILLEGAL_ARGUMENT_ERROR, + [&](UChar* chars, uint32_t size, UErrorCode* status) { + // |uldn_keyValueDisplayName| expects old-style keyword values. + return uldn_keyValueDisplayName(mULocaleDisplayNames.GetConst(), + "calendar", legacyCalendar, chars, + size, status); + }); + if (result.isErr()) { + return Err(ToError(result.unwrapErr())); + } + } else { + aBuffer.written(0); + } + } else { + aBuffer.written(0); + } + + return HandleFallback(aBuffer, aFallback, + [&] { return canonicalCalendar; }); + } + + /** + * Get the localized name of a weekday. This is a MozExtension, and not + * currently part of ECMA-402. + */ + template <typename B> + Result<Ok, DisplayNamesError> GetWeekday( + B& aBuffer, Weekday aWeekday, Span<const char> aCalendar, + Fallback aFallback = Fallback::None) { + // SpiderMonkey static casts the enum, so ensure it is correctly in range. + MOZ_ASSERT(aWeekday >= Weekday::Monday && aWeekday <= Weekday::Sunday); + + UDateFormatSymbolType symbolType; + switch (mOptions.style) { + case DisplayNames::Style::Long: + symbolType = UDAT_STANDALONE_WEEKDAYS; + break; + + case DisplayNames::Style::Abbreviated: + // ICU "short" is CLDR "abbreviated" format. + symbolType = UDAT_STANDALONE_SHORT_WEEKDAYS; + break; + + case DisplayNames::Style::Short: + // ICU "shorter" is CLDR "short" format. + symbolType = UDAT_STANDALONE_SHORTER_WEEKDAYS; + break; + + case DisplayNames::Style::Narrow: + symbolType = UDAT_STANDALONE_NARROW_WEEKDAYS; + break; + } + + static constexpr int32_t indices[] = { + UCAL_MONDAY, UCAL_TUESDAY, UCAL_WEDNESDAY, UCAL_THURSDAY, + UCAL_FRIDAY, UCAL_SATURDAY, UCAL_SUNDAY}; + + if (auto result = ComputeDateTimeDisplayNames( + symbolType, mozilla::Span(indices), aCalendar); + result.isErr()) { + return result.propagateErr(); + } + MOZ_ASSERT(mDateTimeDisplayNames.length() == std::size(indices)); + + auto& name = + mDateTimeDisplayNames[EnumToIndex(std::size(indices), aWeekday)]; + if (!FillBuffer(name.AsSpan(), aBuffer)) { + return Err(DisplayNamesError::OutOfMemory); + } + + // There is no need to fallback, as invalid options are + // DisplayNamesError::InvalidOption. + return Ok(); + } + + /** + * Get the localized name of a month. This is a MozExtension, and not + * currently part of ECMA-402. + */ + template <typename B> + Result<Ok, DisplayNamesError> GetMonth(B& aBuffer, Month aMonth, + Span<const char> aCalendar, + Fallback aFallback = Fallback::None) { + // SpiderMonkey static casts the enum, so ensure it is correctly in range. + MOZ_ASSERT(aMonth >= Month::January && aMonth <= Month::Undecimber); + + UDateFormatSymbolType symbolType; + switch (mOptions.style) { + case DisplayNames::Style::Long: + symbolType = UDAT_STANDALONE_MONTHS; + break; + + case DisplayNames::Style::Abbreviated: + case DisplayNames::Style::Short: + symbolType = UDAT_STANDALONE_SHORT_MONTHS; + break; + + case DisplayNames::Style::Narrow: + symbolType = UDAT_STANDALONE_NARROW_MONTHS; + break; + } + + static constexpr int32_t indices[] = { + UCAL_JANUARY, UCAL_FEBRUARY, UCAL_MARCH, UCAL_APRIL, + UCAL_MAY, UCAL_JUNE, UCAL_JULY, UCAL_AUGUST, + UCAL_SEPTEMBER, UCAL_OCTOBER, UCAL_NOVEMBER, UCAL_DECEMBER, + UCAL_UNDECIMBER}; + + if (auto result = ComputeDateTimeDisplayNames( + symbolType, mozilla::Span(indices), aCalendar); + result.isErr()) { + return result.propagateErr(); + } + MOZ_ASSERT(mDateTimeDisplayNames.length() == std::size(indices)); + auto& name = mDateTimeDisplayNames[EnumToIndex(std::size(indices), aMonth)]; + if (!FillBuffer(Span(name.AsSpan()), aBuffer)) { + return Err(DisplayNamesError::OutOfMemory); + } + + return HandleFallback(aBuffer, aFallback, + [&] { return ToCodeString(aMonth); }); + } + + /** + * Get the localized name of a quarter. This is a MozExtension, and not + * currently part of ECMA-402. + */ + template <typename B> + Result<Ok, DisplayNamesError> GetQuarter( + B& aBuffer, Quarter aQuarter, Span<const char> aCalendar, + Fallback aFallback = Fallback::None) { + // SpiderMonkey static casts the enum, so ensure it is correctly in range. + MOZ_ASSERT(aQuarter >= Quarter::Q1 && aQuarter <= Quarter::Q4); + + UDateFormatSymbolType symbolType; + switch (mOptions.style) { + case DisplayNames::Style::Long: + symbolType = UDAT_STANDALONE_QUARTERS; + break; + + case DisplayNames::Style::Abbreviated: + case DisplayNames::Style::Short: + symbolType = UDAT_STANDALONE_SHORT_QUARTERS; + break; + + case DisplayNames::Style::Narrow: + symbolType = UDAT_STANDALONE_NARROW_QUARTERS; + break; + } + + // ICU doesn't provide an enum for quarters. + static constexpr int32_t indices[] = {0, 1, 2, 3}; + + if (auto result = ComputeDateTimeDisplayNames( + symbolType, mozilla::Span(indices), aCalendar); + result.isErr()) { + return result.propagateErr(); + } + MOZ_ASSERT(mDateTimeDisplayNames.length() == std::size(indices)); + + auto& name = + mDateTimeDisplayNames[EnumToIndex(std::size(indices), aQuarter)]; + if (!FillBuffer(Span(name.AsSpan()), aBuffer)) { + return Err(DisplayNamesError::OutOfMemory); + } + + // There is no need to fallback, as invalid options are + // DisplayNamesError::InvalidOption. + return Ok(); + } + + /** + * Get the localized name of a day period. This is a MozExtension, and not + * currently part of ECMA-402. + */ + template <typename B> + Result<Ok, DisplayNamesError> GetDayPeriod( + B& aBuffer, DayPeriod aDayPeriod, Span<const char> aCalendar, + Fallback aFallback = Fallback::None) { + UDateFormatSymbolType symbolType = UDAT_AM_PMS; + + static constexpr int32_t indices[] = {UCAL_AM, UCAL_PM}; + + if (auto result = ComputeDateTimeDisplayNames( + symbolType, mozilla::Span(indices), aCalendar); + result.isErr()) { + return result.propagateErr(); + } + MOZ_ASSERT(mDateTimeDisplayNames.length() == std::size(indices)); + + auto& name = + mDateTimeDisplayNames[EnumToIndex(std::size(indices), aDayPeriod)]; + if (!FillBuffer(name.AsSpan(), aBuffer)) { + return Err(DisplayNamesError::OutOfMemory); + } + + // There is no need to fallback, as invalid options are + // DisplayNamesError::InvalidOption. + return Ok(); + } + + /** + * Get the localized name of a date time field. + * Part of Intl.DisplayNames V2. https://tc39.es/intl-displaynames-v2/ + * Accepts: + * "era", "year", "quarter", "month", "weekOfYear", "weekday", "day", + * "dayPeriod", "hour", "minute", "second", "timeZoneName" + * Examples: + * "weekday" => "day of the week" + * "dayPeriod" => "AM/PM" + */ + template <typename B> + Result<Ok, DisplayNamesError> GetDateTimeField( + B& aBuffer, DateTimeField aField, + DateTimePatternGenerator& aDateTimePatternGen, + Fallback aFallback = Fallback::None) { + UDateTimePatternField field; + switch (aField) { + case DateTimeField::Era: + field = UDATPG_ERA_FIELD; + break; + case DateTimeField::Year: + field = UDATPG_YEAR_FIELD; + break; + case DateTimeField::Quarter: + field = UDATPG_QUARTER_FIELD; + break; + case DateTimeField::Month: + field = UDATPG_MONTH_FIELD; + break; + case DateTimeField::WeekOfYear: + field = UDATPG_WEEK_OF_YEAR_FIELD; + break; + case DateTimeField::Weekday: + field = UDATPG_WEEKDAY_FIELD; + break; + case DateTimeField::Day: + field = UDATPG_DAY_FIELD; + break; + case DateTimeField::DayPeriod: + field = UDATPG_DAYPERIOD_FIELD; + break; + case DateTimeField::Hour: + field = UDATPG_HOUR_FIELD; + break; + case DateTimeField::Minute: + field = UDATPG_MINUTE_FIELD; + break; + case DateTimeField::Second: + field = UDATPG_SECOND_FIELD; + break; + case DateTimeField::TimeZoneName: + field = UDATPG_ZONE_FIELD; + break; + } + + UDateTimePGDisplayWidth width; + switch (mOptions.style) { + case DisplayNames::Style::Long: + width = UDATPG_WIDE; + break; + case DisplayNames::Style::Abbreviated: + case DisplayNames::Style::Short: + width = UDATPG_ABBREVIATED; + break; + case DisplayNames::Style::Narrow: + width = UDATPG_NARROW; + break; + } + + auto result = FillBufferWithICUCall( + aBuffer, [&](UChar* target, int32_t length, UErrorCode* status) { + return udatpg_getFieldDisplayName( + aDateTimePatternGen.GetUDateTimePatternGenerator(), field, width, + target, length, status); + }); + + if (result.isErr()) { + return Err(ToError(result.unwrapErr())); + } + // There is no need to fallback, as invalid options are + // DisplayNamesError::InvalidOption. + return Ok(); + } + + Options mOptions; + Buffer<char> mLocale; + Vector<Buffer<char16_t>> mDateTimeDisplayNames; + ICUPointer<ULocaleDisplayNames> mULocaleDisplayNames = + ICUPointer<ULocaleDisplayNames>(nullptr); +}; + +} // namespace mozilla::intl + +#endif |