DisplayNames.h (32300B)
1 /* This Source Code Form is subject to the terms of the Mozilla Public 2 * License, v. 2.0. If a copy of the MPL was not distributed with this 3 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ 4 #ifndef intl_components_DisplayNames_h_ 5 #define intl_components_DisplayNames_h_ 6 7 #include <string_view> 8 #include "unicode/udat.h" 9 #include "unicode/udatpg.h" 10 #include "unicode/uldnames.h" 11 #include "unicode/uloc.h" 12 #include "unicode/ucurr.h" 13 #include "mozilla/intl/Calendar.h" 14 #include "mozilla/intl/DateTimePatternGenerator.h" 15 #include "mozilla/intl/ICU4CGlue.h" 16 #include "mozilla/intl/Locale.h" 17 #include "mozilla/Buffer.h" 18 #include "mozilla/Casting.h" 19 #include "mozilla/PodOperations.h" 20 #include "mozilla/Result.h" 21 #include "mozilla/Span.h" 22 #include "mozilla/TextUtils.h" 23 #include "mozilla/UniquePtr.h" 24 25 namespace mozilla::intl { 26 /** 27 * Provide more granular errors for DisplayNames rather than use the generic 28 * ICUError type. This helps with providing more actionable feedback for 29 * errors with input validation. 30 * 31 * This type can't be nested in the DisplayNames class because it needs the 32 * UnusedZero and HasFreeLSB definitions. 33 */ 34 enum class DisplayNamesError { 35 // Since we claim UnusedZero<DisplayNamesError>::value and 36 // HasFreeLSB<Error>::value == true below, we must only use positive, 37 // even enum values. 38 InternalError = 2, 39 OutOfMemory = 4, 40 InvalidOption = 6, 41 DuplicateVariantSubtag = 8, 42 InvalidLanguageTag = 10, 43 }; 44 } // namespace mozilla::intl 45 46 namespace mozilla::detail { 47 // Ensure the efficient packing of the error types into the result. See 48 // ICUError.h and the ICUError comments for more information. 49 template <> 50 struct UnusedZero<intl::DisplayNamesError> 51 : UnusedZeroEnum<intl::DisplayNamesError> {}; 52 53 template <> 54 struct HasFreeLSB<intl::DisplayNamesError> { 55 static constexpr bool value = true; 56 }; 57 } // namespace mozilla::detail 58 59 namespace mozilla::intl { 60 61 // NOTE: The UTF-35 canonical "code" value for months and quarters are 1-based 62 // integers, so some of the following enums are 1-based for consistency with 63 // that. For simplicity, we make all of the following enums 1-based, but use 64 // `EnumToIndex` (see below) to convert to zero based if indexing into internal 65 // (non-ICU) tables. 66 67 /** 68 * Month choices for display names. 69 */ 70 enum class Month : uint8_t { 71 January = 1, 72 February, 73 March, 74 April, 75 May, 76 June, 77 July, 78 August, 79 September, 80 October, 81 November, 82 December, 83 // Some calendar systems feature a 13th month. 84 // https://en.wikipedia.org/wiki/Undecimber 85 Undecimber 86 }; 87 88 /** 89 * Quarter choices for display names. 90 */ 91 enum class Quarter : uint8_t { 92 Q1 = 1, 93 Q2, 94 Q3, 95 Q4, 96 }; 97 98 /** 99 * Day period choices for display names. 100 */ 101 enum class DayPeriod : uint8_t { 102 AM = 1, 103 PM, 104 }; 105 106 /** 107 * DateTimeField choices for display names. 108 */ 109 enum class DateTimeField : uint8_t { 110 Era = 1, 111 Year, 112 Quarter, 113 Month, 114 WeekOfYear, 115 Weekday, 116 Day, 117 DayPeriod, 118 Hour, 119 Minute, 120 Second, 121 TimeZoneName, 122 }; 123 124 /** 125 * DisplayNames provide a way to get the localized names of various types of 126 * information such as the names of the day of the week, months, currency etc. 127 * 128 * This class backs SpiderMonkeys implementation of Intl.DisplayNames 129 * https://tc39.es/ecma402/#intl-displaynames-objects 130 */ 131 class DisplayNames final { 132 public: 133 /** 134 * The style of the display name, specified by the amount of space available 135 * for displaying the text. 136 */ 137 enum class Style { 138 Narrow, 139 Short, 140 Long, 141 // Note: Abbreviated is not part of ECMA-402, but it is available for 142 // internal Mozilla usage. 143 Abbreviated, 144 }; 145 146 /** 147 * Use either standard or dialect names for the "Language" type. 148 */ 149 enum class LanguageDisplay { 150 Standard, 151 Dialect, 152 }; 153 154 /** 155 * Determines the fallback behavior if no match is found. 156 */ 157 enum class Fallback { 158 // The buffer will contain an empty string. 159 None, 160 // The buffer will contain the code, but typically in a canonicalized form. 161 Code 162 }; 163 164 /** 165 * These options correlate to the ECMA-402 DisplayNames options. The defaults 166 * values must match the default initialized values of ECMA-402. The type 167 * option is omitted as the C++ API relies on directly calling the 168 * DisplayNames::Get* methods. 169 * 170 * https://tc39.es/ecma402/#intl-displaynames-objects 171 * https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Intl/DisplayNames 172 */ 173 struct Options { 174 Style style = Style::Long; 175 LanguageDisplay languageDisplay = LanguageDisplay::Standard; 176 }; 177 178 DisplayNames(ULocaleDisplayNames* aDisplayNames, Span<const char> aLocale, 179 Options aOptions) 180 : mOptions(aOptions), mULocaleDisplayNames(aDisplayNames) { 181 MOZ_ASSERT(aDisplayNames); 182 183 // Copy the span and ensure null termination. 184 mLocale = Buffer<char>(aLocale.size() + 1); 185 PodCopy(mLocale.begin(), aLocale.data(), aLocale.size()); 186 mLocale[aLocale.size()] = '\0'; 187 } 188 189 /** 190 * Initialize a new DisplayNames for the provided locale and using the 191 * provided options. 192 * 193 * https://tc39.es/ecma402/#sec-Intl.DisplayNames 194 */ 195 static Result<UniquePtr<DisplayNames>, ICUError> TryCreate( 196 const char* aLocale, Options aOptions); 197 198 // Not copyable or movable 199 DisplayNames(const DisplayNames&) = delete; 200 DisplayNames& operator=(const DisplayNames&) = delete; 201 202 ~DisplayNames(); 203 204 /** 205 * Easily convert to a more specific DisplayNames error. 206 */ 207 DisplayNamesError ToError(ICUError aError) const; 208 209 /** 210 * Easily convert to a more specific DisplayNames error. 211 */ 212 DisplayNamesError ToError(Locale::CanonicalizationError aError) const; 213 214 private: 215 /** 216 * A helper function to handle the fallback behavior, where if there is a 217 * fallback the buffer is filled with the "code", often in canonicalized form. 218 */ 219 template <typename B, typename Fn> 220 static Result<Ok, DisplayNamesError> HandleFallback(B& aBuffer, 221 Fallback aFallback, 222 Fn aGetFallbackSpan) { 223 if (aBuffer.length() == 0 && 224 aFallback == mozilla::intl::DisplayNames::Fallback::Code) { 225 if (!FillBuffer(aGetFallbackSpan(), aBuffer)) { 226 return Err(DisplayNamesError::OutOfMemory); 227 } 228 } 229 return Ok(); 230 } 231 232 /** 233 * This is a specialized form of the FillBufferWithICUCall for DisplayNames. 234 * Different APIs report that no display name is found with different 235 * statuses. This method signals no display name was found by setting the 236 * buffer to 0. 237 * 238 * The display name APIs such as `uldn_scriptDisplayName`, 239 * `uloc_getDisplayScript`, and `uldn_regionDisplayName` report 240 * U_ILLEGAL_ARGUMENT_ERROR when no display name was found. In order to 241 * accomodate fallbacking, return an empty string in this case. 242 */ 243 template <typename B, typename F> 244 static ICUResult FillBufferWithICUDisplayNames( 245 B& aBuffer, UErrorCode aNoDisplayNameStatus, F aCallback) { 246 return FillBufferWithICUCall( 247 aBuffer, [&](UChar* target, int32_t length, UErrorCode* status) { 248 int32_t res = aCallback(target, length, status); 249 250 if (*status == aNoDisplayNameStatus) { 251 *status = U_ZERO_ERROR; 252 res = 0; 253 } 254 return res; 255 }); 256 } 257 258 /** 259 * An internal helper to compute the list of display names for various 260 * DateTime options. 261 */ 262 Result<Ok, DisplayNamesError> ComputeDateTimeDisplayNames( 263 UDateFormatSymbolType symbolType, mozilla::Span<const int32_t> indices, 264 Span<const char> aCalendar); 265 266 // The following are the stack-allocated sizes for various strings using the 267 // mozilla::Vector. The numbers should be large enough to fit the common 268 // cases, and when the strings are too large they will fall back to heap 269 // allocations. 270 271 // Fit BCP 47 locales such as "en-US", "zh-Hant". Locales can get quite long, 272 // but 32 should fit most smaller locales without a lot of extensions. 273 static constexpr size_t LocaleVecLength = 32; 274 // Fit calendar names such as "gregory", "buddhist", "islamic-civil". 275 // "islamic-umalqura" is 16 bytes + 1 for null termination, so round up to 32. 276 static constexpr size_t CalendarVecLength = 32; 277 278 /** 279 * Given an ASCII alpha, convert it to upper case. 280 */ 281 static inline char16_t AsciiAlphaToUpperCase(char16_t aCh) { 282 MOZ_ASSERT(IsAsciiAlpha(aCh)); 283 return AsciiToUpperCase(aCh); 284 }; 285 286 /** 287 * Attempt to use enums to safely index into an array. 288 * 289 * Note: The enums we support here are all defined starting from 1. 290 */ 291 template <typename T> 292 inline int32_t EnumToIndex(size_t aSize, T aEnum) { 293 size_t index = static_cast<size_t>(aEnum) - 1; 294 MOZ_RELEASE_ASSERT(index < aSize, 295 "Enum indexing mismatch for display names."); 296 return index; 297 } 298 299 /** 300 * Convert the month to a numeric code as a string. 301 */ 302 static Span<const char> ToCodeString(Month aMonth); 303 304 public: 305 /** 306 * Get the localized name of a language. Part of ECMA-402. 307 * 308 * Accepts: 309 * languageCode ["-" scriptCode] ["-" regionCode ] *("-" variant ) 310 * Where the language code is: 311 * 1. A two letters ISO 639-1 language code 312 * https://en.wikipedia.org/wiki/List_of_ISO_639-1_codes 313 * 2. A three letters ISO 639-2 language code 314 * https://en.wikipedia.org/wiki/List_of_ISO_639-2_codes 315 * 316 * Examples: 317 * "es-ES" => "European Spanish" (en-US), "español de España" (es-ES) 318 * "zh-Hant" => "Traditional Chinese" (en-US), "chino tradicional" (es-ES) 319 */ 320 template <typename B> 321 Result<Ok, DisplayNamesError> GetLanguage( 322 B& aBuffer, Span<const char> aLanguage, 323 Fallback aFallback = Fallback::None) const { 324 static_assert(std::is_same<typename B::CharType, char16_t>::value); 325 mozilla::intl::Locale tag; 326 if (LocaleParser::TryParseBaseName(aLanguage, tag).isErr()) { 327 return Err(DisplayNamesError::InvalidOption); 328 } 329 330 { 331 // ICU always canonicalizes the input locale, but since we know that ICU's 332 // canonicalization is incomplete, we need to perform our own 333 // canonicalization to ensure consistent result. 334 auto result = tag.CanonicalizeBaseName(); 335 if (result.isErr()) { 336 return Err(ToError(result.unwrapErr())); 337 } 338 } 339 340 Vector<char, DisplayNames::LocaleVecLength> tagVec; 341 { 342 VectorToBufferAdaptor tagBuffer(tagVec); 343 auto result = tag.ToString(tagBuffer); 344 if (result.isErr()) { 345 return Err(ToError(result.unwrapErr())); 346 } 347 if (!tagVec.append('\0')) { 348 // The tag should be null terminated. 349 return Err(DisplayNamesError::OutOfMemory); 350 } 351 } 352 353 auto result = FillBufferWithICUDisplayNames( 354 aBuffer, U_ILLEGAL_ARGUMENT_ERROR, 355 [&](UChar* target, int32_t length, UErrorCode* status) { 356 return uldn_localeDisplayName(mULocaleDisplayNames.GetConst(), 357 tagVec.begin(), target, length, status); 358 }); 359 if (result.isErr()) { 360 return Err(ToError(result.unwrapErr())); 361 } 362 363 return HandleFallback(aBuffer, aFallback, [&] { 364 // Remove the null terminator. 365 return Span(tagVec.begin(), tagVec.length() - 1); 366 }); 367 }; 368 369 /** 370 * Get the localized name of a region. Part of ECMA-402. 371 * 372 * Accepts: 373 * 1. an ISO-3166 two letters: 374 * https://www.iso.org/iso-3166-country-codes.html 375 * 2. region code, or a three digits UN M49 Geographic Regions. 376 * https://unstats.un.org/unsd/methodology/m49/ 377 * 378 * Examples 379 * "US" => "United States" (en-US), "Estados Unidos", (es-ES) 380 * "158" => "Taiwan" (en-US), "Taiwán", (es-ES) 381 */ 382 template <typename B> 383 Result<Ok, DisplayNamesError> GetRegion( 384 B& aBuffer, Span<const char> aCode, 385 Fallback aFallback = Fallback::None) const { 386 static_assert(std::is_same<typename B::CharType, char16_t>::value); 387 388 if (!IsStructurallyValidRegionTag(aCode)) { 389 return Err(DisplayNamesError::InvalidOption); 390 } 391 mozilla::intl::RegionSubtag region{aCode}; 392 393 mozilla::intl::Locale tag; 394 tag.SetLanguage("und"); 395 tag.SetRegion(region); 396 397 { 398 // ICU always canonicalizes the input locale, but since we know that ICU's 399 // canonicalization is incomplete, we need to perform our own 400 // canonicalization to ensure consistent result. 401 auto result = tag.CanonicalizeBaseName(); 402 if (result.isErr()) { 403 return Err(ToError(result.unwrapErr())); 404 } 405 } 406 407 MOZ_ASSERT(tag.Region().Present()); 408 409 // Note: ICU requires the region subtag to be in canonical case. 410 const mozilla::intl::RegionSubtag& canonicalRegion = tag.Region(); 411 412 char regionChars[mozilla::intl::LanguageTagLimits::RegionLength + 1] = {}; 413 std::copy_n(canonicalRegion.Span().data(), canonicalRegion.Length(), 414 regionChars); 415 416 auto result = FillBufferWithICUDisplayNames( 417 aBuffer, U_ILLEGAL_ARGUMENT_ERROR, 418 [&](UChar* chars, uint32_t size, UErrorCode* status) { 419 return uldn_regionDisplayName( 420 mULocaleDisplayNames.GetConst(), regionChars, chars, 421 AssertedCast<int32_t, uint32_t>(size), status); 422 }); 423 424 if (result.isErr()) { 425 return Err(ToError(result.unwrapErr())); 426 } 427 428 return HandleFallback(aBuffer, aFallback, [&] { 429 region.ToUpperCase(); 430 return region.Span(); 431 }); 432 } 433 434 /** 435 * Get the localized name of a currency. Part of ECMA-402. 436 * 437 * Accepts: 438 * A 3-letter ISO 4217 currency code. 439 * https://en.wikipedia.org/wiki/ISO_4217 440 * 441 * Examples: 442 * "EUR" => "Euro" (en-US), "euro" (es_ES), "欧元", (zh) 443 * "JPY" => "Japanese Yen" (en-US), "yen" (es_ES), "日元", (zh) 444 */ 445 template <typename B> 446 Result<Ok, DisplayNamesError> GetCurrency( 447 B& aBuffer, Span<const char> aCurrency, 448 Fallback aFallback = Fallback::None) const { 449 static_assert(std::is_same<typename B::CharType, char16_t>::value); 450 if (aCurrency.size() != 3) { 451 return Err(DisplayNamesError::InvalidOption); 452 } 453 454 if (!mozilla::IsAsciiAlpha(aCurrency[0]) || 455 !mozilla::IsAsciiAlpha(aCurrency[1]) || 456 !mozilla::IsAsciiAlpha(aCurrency[2])) { 457 return Err(DisplayNamesError::InvalidOption); 458 } 459 460 // Normally this type of operation wouldn't be safe, but ASCII characters 461 // all take 1 byte in UTF-8 encoding, and can be zero padded to be valid 462 // UTF-16. Currency codes are all three ASCII letters. 463 // Normalize to upper case so we can easily detect the fallback case. 464 char16_t currency[] = {AsciiAlphaToUpperCase(aCurrency[0]), 465 AsciiAlphaToUpperCase(aCurrency[1]), 466 AsciiAlphaToUpperCase(aCurrency[2]), u'\0'}; 467 468 UCurrNameStyle style; 469 switch (mOptions.style) { 470 case Style::Long: 471 style = UCURR_LONG_NAME; 472 break; 473 case Style::Abbreviated: 474 case Style::Short: 475 style = UCURR_SYMBOL_NAME; 476 break; 477 case Style::Narrow: 478 style = UCURR_NARROW_SYMBOL_NAME; 479 break; 480 } 481 482 int32_t length = 0; 483 UErrorCode status = U_ZERO_ERROR; 484 const char16_t* name = ucurr_getName(currency, IcuLocale(mLocale), style, 485 nullptr, &length, &status); 486 if (U_FAILURE(status)) { 487 return Err(DisplayNamesError::InternalError); 488 } 489 490 // No localized currency name was found when the error code is 491 // U_USING_DEFAULT_WARNING and the returned string is equal to the (upper 492 // case transformed) currency code. When `aFallback` is `Fallback::Code`, 493 // we don't have to perform any additional work, because ICU already 494 // returned the currency code in its normalized, upper case form. 495 if (aFallback == DisplayNames::Fallback::None && 496 status == U_USING_DEFAULT_WARNING && length == 3 && 497 std::u16string_view{name, 3} == std::u16string_view{currency, 3}) { 498 if (aBuffer.length() != 0) { 499 // Ensure an empty string is in the buffer when there is no fallback. 500 aBuffer.written(0); 501 } 502 return Ok(); 503 } 504 505 if (!FillBuffer(Span(name, length), aBuffer)) { 506 return Err(DisplayNamesError::OutOfMemory); 507 } 508 509 return Ok(); 510 } 511 512 /** 513 * Get the localized name of a script. Part of ECMA-402. 514 * 515 * Accepts: 516 * ECMA-402 expects the ISO-15924 four letters script code. 517 * https://unicode.org/iso15924/iso15924-codes.html 518 * e.g. "Latn" 519 * 520 * Examples: 521 * "Cher" => "Cherokee" (en-US), "cherokee" (es-ES) 522 * "Latn" => "Latin" (en-US), "latino" (es-ES) 523 */ 524 template <typename B> 525 Result<Ok, DisplayNamesError> GetScript( 526 B& aBuffer, Span<const char> aScript, 527 Fallback aFallback = Fallback::None) const { 528 static_assert(std::is_same<typename B::CharType, char16_t>::value); 529 530 if (!IsStructurallyValidScriptTag(aScript)) { 531 return Err(DisplayNamesError::InvalidOption); 532 } 533 mozilla::intl::ScriptSubtag script{aScript}; 534 535 mozilla::intl::Locale tag; 536 tag.SetLanguage("und"); 537 tag.SetScript(script); 538 539 { 540 // ICU always canonicalizes the input locale, but since we know that ICU's 541 // canonicalization is incomplete, we need to perform our own 542 // canonicalization to ensure consistent result. 543 auto result = tag.CanonicalizeBaseName(); 544 if (result.isErr()) { 545 return Err(ToError(result.unwrapErr())); 546 } 547 } 548 549 MOZ_ASSERT(tag.Script().Present()); 550 mozilla::Vector<char, DisplayNames::LocaleVecLength> tagString; 551 VectorToBufferAdaptor buffer(tagString); 552 553 switch (mOptions.style) { 554 case Style::Long: { 555 // |uldn_scriptDisplayName| doesn't use the stand-alone form for script 556 // subtags, so we're using |uloc_getDisplayScript| instead. (This only 557 // applies to the long form.) 558 // 559 // ICU bug: https://unicode-org.atlassian.net/browse/ICU-9301 560 561 // |uloc_getDisplayScript| expects a full locale identifier as its 562 // input. 563 if (auto result = tag.ToString(buffer); result.isErr()) { 564 return Err(ToError(result.unwrapErr())); 565 } 566 567 // Null terminate the tag string. 568 if (!tagString.append('\0')) { 569 return Err(DisplayNamesError::OutOfMemory); 570 } 571 572 auto result = FillBufferWithICUDisplayNames( 573 aBuffer, U_USING_DEFAULT_WARNING, 574 [&](UChar* target, int32_t length, UErrorCode* status) { 575 return uloc_getDisplayScript(tagString.begin(), 576 IcuLocale(mLocale), target, length, 577 status); 578 }); 579 580 if (result.isErr()) { 581 return Err(ToError(result.unwrapErr())); 582 } 583 break; 584 } 585 case Style::Abbreviated: 586 case Style::Short: 587 case Style::Narrow: { 588 // Note: ICU requires the script subtag to be in canonical case. 589 const mozilla::intl::ScriptSubtag& canonicalScript = tag.Script(); 590 591 char scriptChars[mozilla::intl::LanguageTagLimits::ScriptLength + 1] = 592 {}; 593 MOZ_ASSERT(canonicalScript.Length() <= 594 mozilla::intl::LanguageTagLimits::ScriptLength + 1); 595 std::copy_n(canonicalScript.Span().data(), canonicalScript.Length(), 596 scriptChars); 597 598 auto result = FillBufferWithICUDisplayNames( 599 aBuffer, U_ILLEGAL_ARGUMENT_ERROR, 600 [&](UChar* target, int32_t length, UErrorCode* status) { 601 return uldn_scriptDisplayName(mULocaleDisplayNames.GetConst(), 602 scriptChars, target, length, 603 status); 604 }); 605 606 if (result.isErr()) { 607 return Err(ToError(result.unwrapErr())); 608 } 609 break; 610 } 611 } 612 613 return HandleFallback(aBuffer, aFallback, [&] { 614 script.ToTitleCase(); 615 return script.Span(); 616 }); 617 }; 618 619 /** 620 * Get the localized name of a calendar. 621 * Part of Intl.DisplayNames V2. https://tc39.es/intl-displaynames-v2/ 622 * Accepts: 623 * Unicode calendar key: 624 * https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Intl/Locale/calendar#unicode_calendar_keys 625 */ 626 template <typename B> 627 Result<Ok, DisplayNamesError> GetCalendar( 628 B& aBuffer, Span<const char> aCalendar, 629 Fallback aFallback = Fallback::None) const { 630 if (aCalendar.empty() || !IsAscii(aCalendar)) { 631 return Err(DisplayNamesError::InvalidOption); 632 } 633 634 if (LocaleParser::CanParseUnicodeExtensionType(aCalendar).isErr()) { 635 return Err(DisplayNamesError::InvalidOption); 636 } 637 638 // Convert into canonical case before searching for replacements. 639 Vector<char, DisplayNames::CalendarVecLength> lowerCaseCalendar; 640 for (size_t i = 0; i < aCalendar.size(); i++) { 641 if (!lowerCaseCalendar.append(AsciiToLowerCase(aCalendar[i]))) { 642 return Err(DisplayNamesError::OutOfMemory); 643 } 644 } 645 if (!lowerCaseCalendar.append('\0')) { 646 return Err(DisplayNamesError::OutOfMemory); 647 } 648 649 Span<const char> canonicalCalendar = mozilla::Span( 650 lowerCaseCalendar.begin(), lowerCaseCalendar.length() - 1); 651 652 // Search if there's a replacement for the Unicode calendar keyword. 653 { 654 Span<const char> key = mozilla::MakeStringSpan("ca"); 655 Span<const char> type = canonicalCalendar; 656 if (const char* replacement = 657 mozilla::intl::Locale::ReplaceUnicodeExtensionType(key, type)) { 658 canonicalCalendar = MakeStringSpan(replacement); 659 } 660 } 661 662 // The input calendar name is user-controlled, so be extra cautious before 663 // passing arbitrarily large strings to ICU. 664 static constexpr size_t maximumCalendarLength = 100; 665 666 if (canonicalCalendar.size() <= maximumCalendarLength) { 667 // |uldn_keyValueDisplayName| expects old-style keyword values. 668 if (const char* legacyCalendar = 669 uloc_toLegacyType("calendar", canonicalCalendar.Elements())) { 670 auto result = FillBufferWithICUDisplayNames( 671 aBuffer, U_ILLEGAL_ARGUMENT_ERROR, 672 [&](UChar* chars, uint32_t size, UErrorCode* status) { 673 // |uldn_keyValueDisplayName| expects old-style keyword values. 674 return uldn_keyValueDisplayName(mULocaleDisplayNames.GetConst(), 675 "calendar", legacyCalendar, chars, 676 size, status); 677 }); 678 if (result.isErr()) { 679 return Err(ToError(result.unwrapErr())); 680 } 681 } else { 682 aBuffer.written(0); 683 } 684 } else { 685 aBuffer.written(0); 686 } 687 688 return HandleFallback(aBuffer, aFallback, 689 [&] { return canonicalCalendar; }); 690 } 691 692 /** 693 * Get the localized name of a weekday. This is a MozExtension, and not 694 * currently part of ECMA-402. 695 */ 696 template <typename B> 697 Result<Ok, DisplayNamesError> GetWeekday( 698 B& aBuffer, Weekday aWeekday, Span<const char> aCalendar, 699 Fallback aFallback = Fallback::None) { 700 // SpiderMonkey static casts the enum, so ensure it is correctly in range. 701 MOZ_ASSERT(aWeekday >= Weekday::Monday && aWeekday <= Weekday::Sunday); 702 703 UDateFormatSymbolType symbolType; 704 switch (mOptions.style) { 705 case DisplayNames::Style::Long: 706 symbolType = UDAT_STANDALONE_WEEKDAYS; 707 break; 708 709 case DisplayNames::Style::Abbreviated: 710 // ICU "short" is CLDR "abbreviated" format. 711 symbolType = UDAT_STANDALONE_SHORT_WEEKDAYS; 712 break; 713 714 case DisplayNames::Style::Short: 715 // ICU "shorter" is CLDR "short" format. 716 symbolType = UDAT_STANDALONE_SHORTER_WEEKDAYS; 717 break; 718 719 case DisplayNames::Style::Narrow: 720 symbolType = UDAT_STANDALONE_NARROW_WEEKDAYS; 721 break; 722 } 723 724 static constexpr int32_t indices[] = { 725 UCAL_MONDAY, UCAL_TUESDAY, UCAL_WEDNESDAY, UCAL_THURSDAY, 726 UCAL_FRIDAY, UCAL_SATURDAY, UCAL_SUNDAY}; 727 728 if (auto result = ComputeDateTimeDisplayNames( 729 symbolType, mozilla::Span(indices), aCalendar); 730 result.isErr()) { 731 return result.propagateErr(); 732 } 733 MOZ_ASSERT(mDateTimeDisplayNames.length() == std::size(indices)); 734 735 auto& name = 736 mDateTimeDisplayNames[EnumToIndex(std::size(indices), aWeekday)]; 737 if (!FillBuffer(name.AsSpan(), aBuffer)) { 738 return Err(DisplayNamesError::OutOfMemory); 739 } 740 741 // There is no need to fallback, as invalid options are 742 // DisplayNamesError::InvalidOption. 743 return Ok(); 744 } 745 746 /** 747 * Get the localized name of a month. This is a MozExtension, and not 748 * currently part of ECMA-402. 749 */ 750 template <typename B> 751 Result<Ok, DisplayNamesError> GetMonth(B& aBuffer, Month aMonth, 752 Span<const char> aCalendar, 753 Fallback aFallback = Fallback::None) { 754 // SpiderMonkey static casts the enum, so ensure it is correctly in range. 755 MOZ_ASSERT(aMonth >= Month::January && aMonth <= Month::Undecimber); 756 757 UDateFormatSymbolType symbolType; 758 switch (mOptions.style) { 759 case DisplayNames::Style::Long: 760 symbolType = UDAT_STANDALONE_MONTHS; 761 break; 762 763 case DisplayNames::Style::Abbreviated: 764 case DisplayNames::Style::Short: 765 symbolType = UDAT_STANDALONE_SHORT_MONTHS; 766 break; 767 768 case DisplayNames::Style::Narrow: 769 symbolType = UDAT_STANDALONE_NARROW_MONTHS; 770 break; 771 } 772 773 static constexpr int32_t indices[] = { 774 UCAL_JANUARY, UCAL_FEBRUARY, UCAL_MARCH, UCAL_APRIL, 775 UCAL_MAY, UCAL_JUNE, UCAL_JULY, UCAL_AUGUST, 776 UCAL_SEPTEMBER, UCAL_OCTOBER, UCAL_NOVEMBER, UCAL_DECEMBER, 777 UCAL_UNDECIMBER}; 778 779 if (auto result = ComputeDateTimeDisplayNames( 780 symbolType, mozilla::Span(indices), aCalendar); 781 result.isErr()) { 782 return result.propagateErr(); 783 } 784 MOZ_ASSERT(mDateTimeDisplayNames.length() == std::size(indices)); 785 auto& name = mDateTimeDisplayNames[EnumToIndex(std::size(indices), aMonth)]; 786 if (!FillBuffer(Span(name.AsSpan()), aBuffer)) { 787 return Err(DisplayNamesError::OutOfMemory); 788 } 789 790 return HandleFallback(aBuffer, aFallback, 791 [&] { return ToCodeString(aMonth); }); 792 } 793 794 /** 795 * Get the localized name of a quarter. This is a MozExtension, and not 796 * currently part of ECMA-402. 797 */ 798 template <typename B> 799 Result<Ok, DisplayNamesError> GetQuarter( 800 B& aBuffer, Quarter aQuarter, Span<const char> aCalendar, 801 Fallback aFallback = Fallback::None) { 802 // SpiderMonkey static casts the enum, so ensure it is correctly in range. 803 MOZ_ASSERT(aQuarter >= Quarter::Q1 && aQuarter <= Quarter::Q4); 804 805 UDateFormatSymbolType symbolType; 806 switch (mOptions.style) { 807 case DisplayNames::Style::Long: 808 symbolType = UDAT_STANDALONE_QUARTERS; 809 break; 810 811 case DisplayNames::Style::Abbreviated: 812 case DisplayNames::Style::Short: 813 symbolType = UDAT_STANDALONE_SHORT_QUARTERS; 814 break; 815 816 case DisplayNames::Style::Narrow: 817 symbolType = UDAT_STANDALONE_NARROW_QUARTERS; 818 break; 819 } 820 821 // ICU doesn't provide an enum for quarters. 822 static constexpr int32_t indices[] = {0, 1, 2, 3}; 823 824 if (auto result = ComputeDateTimeDisplayNames( 825 symbolType, mozilla::Span(indices), aCalendar); 826 result.isErr()) { 827 return result.propagateErr(); 828 } 829 MOZ_ASSERT(mDateTimeDisplayNames.length() == std::size(indices)); 830 831 auto& name = 832 mDateTimeDisplayNames[EnumToIndex(std::size(indices), aQuarter)]; 833 if (!FillBuffer(Span(name.AsSpan()), aBuffer)) { 834 return Err(DisplayNamesError::OutOfMemory); 835 } 836 837 // There is no need to fallback, as invalid options are 838 // DisplayNamesError::InvalidOption. 839 return Ok(); 840 } 841 842 /** 843 * Get the localized name of a day period. This is a MozExtension, and not 844 * currently part of ECMA-402. 845 */ 846 template <typename B> 847 Result<Ok, DisplayNamesError> GetDayPeriod( 848 B& aBuffer, DayPeriod aDayPeriod, Span<const char> aCalendar, 849 Fallback aFallback = Fallback::None) { 850 UDateFormatSymbolType symbolType; 851 switch (mOptions.style) { 852 case DisplayNames::Style::Long: 853 #ifndef U_HIDE_DRAFT_API 854 symbolType = UDAT_AM_PMS_WIDE; 855 #else 856 symbolType = UDAT_AM_PMS; 857 #endif 858 break; 859 860 case DisplayNames::Style::Abbreviated: 861 case DisplayNames::Style::Short: 862 symbolType = UDAT_AM_PMS; 863 break; 864 865 case DisplayNames::Style::Narrow: 866 #ifndef U_HIDE_DRAFT_API 867 symbolType = UDAT_AM_PMS_NARROW; 868 #else 869 symbolType = UDAT_AM_PMS; 870 #endif 871 break; 872 } 873 874 static constexpr int32_t indices[] = {UCAL_AM, UCAL_PM}; 875 876 if (auto result = ComputeDateTimeDisplayNames( 877 symbolType, mozilla::Span(indices), aCalendar); 878 result.isErr()) { 879 return result.propagateErr(); 880 } 881 MOZ_ASSERT(mDateTimeDisplayNames.length() == std::size(indices)); 882 883 auto& name = 884 mDateTimeDisplayNames[EnumToIndex(std::size(indices), aDayPeriod)]; 885 if (!FillBuffer(name.AsSpan(), aBuffer)) { 886 return Err(DisplayNamesError::OutOfMemory); 887 } 888 889 // There is no need to fallback, as invalid options are 890 // DisplayNamesError::InvalidOption. 891 return Ok(); 892 } 893 894 /** 895 * Get the localized name of a date time field. 896 * Part of Intl.DisplayNames V2. https://tc39.es/intl-displaynames-v2/ 897 * Accepts: 898 * "era", "year", "quarter", "month", "weekOfYear", "weekday", "day", 899 * "dayPeriod", "hour", "minute", "second", "timeZoneName" 900 * Examples: 901 * "weekday" => "day of the week" 902 * "dayPeriod" => "AM/PM" 903 */ 904 template <typename B> 905 Result<Ok, DisplayNamesError> GetDateTimeField( 906 B& aBuffer, DateTimeField aField, 907 DateTimePatternGenerator& aDateTimePatternGen, 908 Fallback aFallback = Fallback::None) { 909 UDateTimePatternField field; 910 switch (aField) { 911 case DateTimeField::Era: 912 field = UDATPG_ERA_FIELD; 913 break; 914 case DateTimeField::Year: 915 field = UDATPG_YEAR_FIELD; 916 break; 917 case DateTimeField::Quarter: 918 field = UDATPG_QUARTER_FIELD; 919 break; 920 case DateTimeField::Month: 921 field = UDATPG_MONTH_FIELD; 922 break; 923 case DateTimeField::WeekOfYear: 924 field = UDATPG_WEEK_OF_YEAR_FIELD; 925 break; 926 case DateTimeField::Weekday: 927 field = UDATPG_WEEKDAY_FIELD; 928 break; 929 case DateTimeField::Day: 930 field = UDATPG_DAY_FIELD; 931 break; 932 case DateTimeField::DayPeriod: 933 field = UDATPG_DAYPERIOD_FIELD; 934 break; 935 case DateTimeField::Hour: 936 field = UDATPG_HOUR_FIELD; 937 break; 938 case DateTimeField::Minute: 939 field = UDATPG_MINUTE_FIELD; 940 break; 941 case DateTimeField::Second: 942 field = UDATPG_SECOND_FIELD; 943 break; 944 case DateTimeField::TimeZoneName: 945 field = UDATPG_ZONE_FIELD; 946 break; 947 } 948 949 UDateTimePGDisplayWidth width; 950 switch (mOptions.style) { 951 case DisplayNames::Style::Long: 952 width = UDATPG_WIDE; 953 break; 954 case DisplayNames::Style::Abbreviated: 955 case DisplayNames::Style::Short: 956 width = UDATPG_ABBREVIATED; 957 break; 958 case DisplayNames::Style::Narrow: 959 width = UDATPG_NARROW; 960 break; 961 } 962 963 auto result = FillBufferWithICUCall( 964 aBuffer, [&](UChar* target, int32_t length, UErrorCode* status) { 965 return udatpg_getFieldDisplayName( 966 aDateTimePatternGen.GetUDateTimePatternGenerator(), field, width, 967 target, length, status); 968 }); 969 970 if (result.isErr()) { 971 return Err(ToError(result.unwrapErr())); 972 } 973 // There is no need to fallback, as invalid options are 974 // DisplayNamesError::InvalidOption. 975 return Ok(); 976 } 977 978 Options mOptions; 979 Buffer<char> mLocale; 980 Vector<Buffer<char16_t>> mDateTimeDisplayNames; 981 ICUPointer<ULocaleDisplayNames> mULocaleDisplayNames = 982 ICUPointer<ULocaleDisplayNames>(nullptr); 983 }; 984 985 } // namespace mozilla::intl 986 987 #endif