DisplayNames.cpp (7910B)
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 #include "mozilla/intl/DisplayNames.h" 5 #include "ScopedICUObject.h" 6 7 namespace mozilla::intl { 8 9 DisplayNames::~DisplayNames() { 10 // The mDisplayNames will not exist when the DisplayNames is being 11 // moved. 12 if (auto* uldn = mULocaleDisplayNames.GetMut()) { 13 uldn_close(uldn); 14 } 15 } 16 17 DisplayNamesError DisplayNames::ToError(ICUError aError) const { 18 switch (aError) { 19 case ICUError::InternalError: 20 case ICUError::OverflowError: 21 return DisplayNamesError::InternalError; 22 case ICUError::OutOfMemory: 23 return DisplayNamesError::OutOfMemory; 24 } 25 MOZ_ASSERT_UNREACHABLE(); 26 return DisplayNamesError::InternalError; 27 } 28 29 DisplayNamesError DisplayNames::ToError( 30 Locale::CanonicalizationError aError) const { 31 switch (aError) { 32 case Locale::CanonicalizationError::DuplicateVariant: 33 return DisplayNamesError::DuplicateVariantSubtag; 34 case Locale::CanonicalizationError::InternalError: 35 return DisplayNamesError::InternalError; 36 case Locale::CanonicalizationError::OutOfMemory: 37 return DisplayNamesError::OutOfMemory; 38 } 39 MOZ_ASSERT_UNREACHABLE(); 40 return DisplayNamesError::InternalError; 41 } 42 43 /* static */ 44 Result<UniquePtr<DisplayNames>, ICUError> DisplayNames::TryCreate( 45 const char* aLocale, Options aOptions) { 46 UErrorCode status = U_ZERO_ERROR; 47 UDisplayContext contexts[] = { 48 // Use either standard or dialect names. 49 // For example either "English (GB)" or "British English". 50 aOptions.languageDisplay == DisplayNames::LanguageDisplay::Standard 51 ? UDISPCTX_STANDARD_NAMES 52 : UDISPCTX_DIALECT_NAMES, 53 54 // Assume the display names are used in a stand-alone context. 55 UDISPCTX_CAPITALIZATION_FOR_STANDALONE, 56 57 // Select either the long or short form. There's no separate narrow form 58 // available in ICU, therefore we equate "narrow"/"short" styles here. 59 aOptions.style == DisplayNames::Style::Long ? UDISPCTX_LENGTH_FULL 60 : UDISPCTX_LENGTH_SHORT, 61 62 // Don't apply substitutes, because we need to apply our own fallbacks. 63 UDISPCTX_NO_SUBSTITUTE, 64 }; 65 66 const char* locale = IcuLocale(aLocale); 67 68 ULocaleDisplayNames* uLocaleDisplayNames = 69 uldn_openForContext(locale, contexts, std::size(contexts), &status); 70 71 if (U_FAILURE(status)) { 72 return Err(ToICUError(status)); 73 } 74 return MakeUnique<DisplayNames>(uLocaleDisplayNames, MakeStringSpan(locale), 75 aOptions); 76 }; 77 78 #ifdef DEBUG 79 static bool IsStandaloneMonth(UDateFormatSymbolType symbolType) { 80 switch (symbolType) { 81 case UDAT_STANDALONE_MONTHS: 82 case UDAT_STANDALONE_SHORT_MONTHS: 83 case UDAT_STANDALONE_NARROW_MONTHS: 84 return true; 85 86 case UDAT_ERAS: 87 case UDAT_MONTHS: 88 case UDAT_SHORT_MONTHS: 89 case UDAT_WEEKDAYS: 90 case UDAT_SHORT_WEEKDAYS: 91 case UDAT_AM_PMS: 92 # ifndef U_HIDE_DRAFT_API 93 case UDAT_AM_PMS_NARROW: 94 case UDAT_AM_PMS_WIDE: 95 # endif 96 case UDAT_LOCALIZED_CHARS: 97 case UDAT_ERA_NAMES: 98 case UDAT_NARROW_MONTHS: 99 case UDAT_NARROW_WEEKDAYS: 100 case UDAT_STANDALONE_WEEKDAYS: 101 case UDAT_STANDALONE_SHORT_WEEKDAYS: 102 case UDAT_STANDALONE_NARROW_WEEKDAYS: 103 case UDAT_QUARTERS: 104 case UDAT_SHORT_QUARTERS: 105 case UDAT_STANDALONE_QUARTERS: 106 case UDAT_STANDALONE_SHORT_QUARTERS: 107 case UDAT_SHORTER_WEEKDAYS: 108 case UDAT_STANDALONE_SHORTER_WEEKDAYS: 109 case UDAT_CYCLIC_YEARS_WIDE: 110 case UDAT_CYCLIC_YEARS_ABBREVIATED: 111 case UDAT_CYCLIC_YEARS_NARROW: 112 case UDAT_ZODIAC_NAMES_WIDE: 113 case UDAT_ZODIAC_NAMES_ABBREVIATED: 114 case UDAT_ZODIAC_NAMES_NARROW: 115 case UDAT_NARROW_QUARTERS: 116 case UDAT_STANDALONE_NARROW_QUARTERS: 117 return false; 118 } 119 120 MOZ_ASSERT_UNREACHABLE("unenumerated, undocumented symbol type"); 121 return false; 122 } 123 #endif 124 125 Result<Ok, DisplayNamesError> DisplayNames::ComputeDateTimeDisplayNames( 126 UDateFormatSymbolType symbolType, mozilla::Span<const int32_t> indices, 127 Span<const char> aCalendar) { 128 if (!mDateTimeDisplayNames.empty()) { 129 // No need to re-compute the display names. 130 return Ok(); 131 } 132 mozilla::intl::Locale tag; 133 // Do not use mLocale.AsSpan() as it includes the null terminator inside the 134 // span. 135 if (LocaleParser::TryParse(Span(mLocale.Elements(), mLocale.Length() - 1), 136 tag) 137 .isErr()) { 138 return Err(DisplayNamesError::InvalidLanguageTag); 139 } 140 141 if (!aCalendar.empty()) { 142 // Add the calendar extension to the locale. This is only available via 143 // the MozExtension. 144 Vector<char, 32> extension; 145 Span<const char> prefix = MakeStringSpan("u-ca-"); 146 if (!extension.append(prefix.data(), prefix.size()) || 147 !extension.append(aCalendar.data(), aCalendar.size())) { 148 return Err(DisplayNamesError::OutOfMemory); 149 } 150 // This overwrites any other Unicode extensions, but should be okay to do 151 // here. 152 if (auto result = tag.SetUnicodeExtension(extension); result.isErr()) { 153 return Err(ToError(result.unwrapErr())); 154 } 155 } 156 157 constexpr char16_t* timeZone = nullptr; 158 constexpr int32_t timeZoneLength = 0; 159 160 constexpr char16_t* pattern = nullptr; 161 constexpr int32_t patternLength = 0; 162 163 Vector<char, DisplayNames::LocaleVecLength> localeWithCalendar; 164 VectorToBufferAdaptor buffer(localeWithCalendar); 165 if (auto result = tag.ToString(buffer); result.isErr()) { 166 return Err(ToError(result.unwrapErr())); 167 } 168 if (!localeWithCalendar.append('\0')) { 169 return Err(DisplayNamesError::OutOfMemory); 170 } 171 172 UErrorCode status = U_ZERO_ERROR; 173 UDateFormat* fmt = udat_open( 174 UDAT_DEFAULT, UDAT_DEFAULT, 175 IcuLocale( 176 // IcuLocale takes a Span that does not include the null terminator. 177 Span(localeWithCalendar.begin(), localeWithCalendar.length() - 1)), 178 timeZone, timeZoneLength, pattern, patternLength, &status); 179 if (U_FAILURE(status)) { 180 return Err(DisplayNamesError::InternalError); 181 } 182 ScopedICUObject<UDateFormat, udat_close> datToClose(fmt); 183 184 Vector<char16_t, DisplayNames::LocaleVecLength> name; 185 for (int32_t index : indices) { 186 auto result = FillBufferWithICUCall(name, [&](UChar* target, int32_t length, 187 UErrorCode* status) { 188 return udat_getSymbols(fmt, symbolType, index, target, length, status); 189 }); 190 if (result.isErr()) { 191 return Err(ToError(result.unwrapErr())); 192 } 193 194 // Everything except Undecimber should always have a non-empty name. 195 MOZ_ASSERT_IF(!IsStandaloneMonth(symbolType) || index != UCAL_UNDECIMBER, 196 !name.empty()); 197 198 if (!mDateTimeDisplayNames.emplaceBack(Span(name.begin(), name.length()))) { 199 return Err(DisplayNamesError::OutOfMemory); 200 } 201 } 202 return Ok(); 203 } 204 205 Span<const char> DisplayNames::ToCodeString(Month aMonth) { 206 switch (aMonth) { 207 case Month::January: 208 return MakeStringSpan("1"); 209 case Month::February: 210 return MakeStringSpan("2"); 211 case Month::March: 212 return MakeStringSpan("3"); 213 case Month::April: 214 return MakeStringSpan("4"); 215 case Month::May: 216 return MakeStringSpan("5"); 217 case Month::June: 218 return MakeStringSpan("6"); 219 case Month::July: 220 return MakeStringSpan("7"); 221 case Month::August: 222 return MakeStringSpan("8"); 223 case Month::September: 224 return MakeStringSpan("9"); 225 case Month::October: 226 return MakeStringSpan("10"); 227 case Month::November: 228 return MakeStringSpan("11"); 229 case Month::December: 230 return MakeStringSpan("12"); 231 case Month::Undecimber: 232 return MakeStringSpan("13"); 233 } 234 MOZ_ASSERT_UNREACHABLE(); 235 return MakeStringSpan("1"); 236 }; 237 238 } // namespace mozilla::intl