summaryrefslogtreecommitdiffstats
path: root/intl/components/src/DisplayNames.cpp
blob: 252969ccbbb03ab8aa648440f9477fa546477dcc (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
/* 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/. */
#include "mozilla/intl/DisplayNames.h"
#include "ScopedICUObject.h"

namespace mozilla::intl {

DisplayNames::~DisplayNames() {
  // The mDisplayNames will not exist when the DisplayNames is being
  // moved.
  if (auto* uldn = mULocaleDisplayNames.GetMut()) {
    uldn_close(uldn);
  }
}

DisplayNamesError DisplayNames::ToError(ICUError aError) const {
  switch (aError) {
    case ICUError::InternalError:
    case ICUError::OverflowError:
      return DisplayNamesError::InternalError;
    case ICUError::OutOfMemory:
      return DisplayNamesError::OutOfMemory;
  }
  MOZ_ASSERT_UNREACHABLE();
  return DisplayNamesError::InternalError;
}

DisplayNamesError DisplayNames::ToError(
    Locale::CanonicalizationError aError) const {
  switch (aError) {
    case Locale::CanonicalizationError::DuplicateVariant:
      return DisplayNamesError::DuplicateVariantSubtag;
    case Locale::CanonicalizationError::InternalError:
      return DisplayNamesError::InternalError;
    case Locale::CanonicalizationError::OutOfMemory:
      return DisplayNamesError::OutOfMemory;
  }
  MOZ_ASSERT_UNREACHABLE();
  return DisplayNamesError::InternalError;
}

/* static */
Result<UniquePtr<DisplayNames>, ICUError> DisplayNames::TryCreate(
    const char* aLocale, Options aOptions) {
  UErrorCode status = U_ZERO_ERROR;
  UDisplayContext contexts[] = {
      // Use either standard or dialect names.
      // For example either "English (GB)" or "British English".
      aOptions.languageDisplay == DisplayNames::LanguageDisplay::Standard
          ? UDISPCTX_STANDARD_NAMES
          : UDISPCTX_DIALECT_NAMES,

      // Assume the display names are used in a stand-alone context.
      UDISPCTX_CAPITALIZATION_FOR_STANDALONE,

      // Select either the long or short form. There's no separate narrow form
      // available in ICU, therefore we equate "narrow"/"short" styles here.
      aOptions.style == DisplayNames::Style::Long ? UDISPCTX_LENGTH_FULL
                                                  : UDISPCTX_LENGTH_SHORT,

      // Don't apply substitutes, because we need to apply our own fallbacks.
      UDISPCTX_NO_SUBSTITUTE,
  };

  const char* locale = IcuLocale(aLocale);

  ULocaleDisplayNames* uLocaleDisplayNames =
      uldn_openForContext(locale, contexts, std::size(contexts), &status);

  if (U_FAILURE(status)) {
    return Err(ToICUError(status));
  }
  return MakeUnique<DisplayNames>(uLocaleDisplayNames, MakeStringSpan(locale),
                                  aOptions);
};

#ifdef DEBUG
static bool IsStandaloneMonth(UDateFormatSymbolType symbolType) {
  switch (symbolType) {
    case UDAT_STANDALONE_MONTHS:
    case UDAT_STANDALONE_SHORT_MONTHS:
    case UDAT_STANDALONE_NARROW_MONTHS:
      return true;

    case UDAT_ERAS:
    case UDAT_MONTHS:
    case UDAT_SHORT_MONTHS:
    case UDAT_WEEKDAYS:
    case UDAT_SHORT_WEEKDAYS:
    case UDAT_AM_PMS:
    case UDAT_LOCALIZED_CHARS:
    case UDAT_ERA_NAMES:
    case UDAT_NARROW_MONTHS:
    case UDAT_NARROW_WEEKDAYS:
    case UDAT_STANDALONE_WEEKDAYS:
    case UDAT_STANDALONE_SHORT_WEEKDAYS:
    case UDAT_STANDALONE_NARROW_WEEKDAYS:
    case UDAT_QUARTERS:
    case UDAT_SHORT_QUARTERS:
    case UDAT_STANDALONE_QUARTERS:
    case UDAT_STANDALONE_SHORT_QUARTERS:
    case UDAT_SHORTER_WEEKDAYS:
    case UDAT_STANDALONE_SHORTER_WEEKDAYS:
    case UDAT_CYCLIC_YEARS_WIDE:
    case UDAT_CYCLIC_YEARS_ABBREVIATED:
    case UDAT_CYCLIC_YEARS_NARROW:
    case UDAT_ZODIAC_NAMES_WIDE:
    case UDAT_ZODIAC_NAMES_ABBREVIATED:
    case UDAT_ZODIAC_NAMES_NARROW:
    case UDAT_NARROW_QUARTERS:
    case UDAT_STANDALONE_NARROW_QUARTERS:
      return false;
  }

  MOZ_ASSERT_UNREACHABLE("unenumerated, undocumented symbol type");
  return false;
}
#endif

Result<Ok, DisplayNamesError> DisplayNames::ComputeDateTimeDisplayNames(
    UDateFormatSymbolType symbolType, mozilla::Span<const int32_t> indices,
    Span<const char> aCalendar) {
  if (!mDateTimeDisplayNames.empty()) {
    // No need to re-compute the display names.
    return Ok();
  }
  mozilla::intl::Locale tag;
  // Do not use mLocale.AsSpan() as it includes the null terminator inside the
  // span.
  if (LocaleParser::TryParse(Span(mLocale.Elements(), mLocale.Length() - 1),
                             tag)
          .isErr()) {
    return Err(DisplayNamesError::InvalidLanguageTag);
  }

  if (!aCalendar.empty()) {
    // Add the calendar extension to the locale. This is only available via
    // the MozExtension.
    Vector<char, 32> extension;
    Span<const char> prefix = MakeStringSpan("u-ca-");
    if (!extension.append(prefix.data(), prefix.size()) ||
        !extension.append(aCalendar.data(), aCalendar.size())) {
      return Err(DisplayNamesError::OutOfMemory);
    }
    // This overwrites any other Unicode extensions, but should be okay to do
    // here.
    if (auto result = tag.SetUnicodeExtension(extension); result.isErr()) {
      return Err(ToError(result.unwrapErr()));
    }
  }

  constexpr char16_t* timeZone = nullptr;
  constexpr int32_t timeZoneLength = 0;

  constexpr char16_t* pattern = nullptr;
  constexpr int32_t patternLength = 0;

  Vector<char, DisplayNames::LocaleVecLength> localeWithCalendar;
  VectorToBufferAdaptor buffer(localeWithCalendar);
  if (auto result = tag.ToString(buffer); result.isErr()) {
    return Err(ToError(result.unwrapErr()));
  }
  if (!localeWithCalendar.append('\0')) {
    return Err(DisplayNamesError::OutOfMemory);
  }

  UErrorCode status = U_ZERO_ERROR;
  UDateFormat* fmt = udat_open(
      UDAT_DEFAULT, UDAT_DEFAULT,
      IcuLocale(
          // IcuLocale takes a Span that does not include the null terminator.
          Span(localeWithCalendar.begin(), localeWithCalendar.length() - 1)),
      timeZone, timeZoneLength, pattern, patternLength, &status);
  if (U_FAILURE(status)) {
    return Err(DisplayNamesError::InternalError);
  }
  ScopedICUObject<UDateFormat, udat_close> datToClose(fmt);

  Vector<char16_t, DisplayNames::LocaleVecLength> name;
  for (int32_t index : indices) {
    auto result = FillBufferWithICUCall(name, [&](UChar* target, int32_t length,
                                                  UErrorCode* status) {
      return udat_getSymbols(fmt, symbolType, index, target, length, status);
    });
    if (result.isErr()) {
      return Err(ToError(result.unwrapErr()));
    }

    // Everything except Undecimber should always have a non-empty name.
    MOZ_ASSERT_IF(!IsStandaloneMonth(symbolType) || index != UCAL_UNDECIMBER,
                  !name.empty());

    if (!mDateTimeDisplayNames.emplaceBack(Span(name.begin(), name.length()))) {
      return Err(DisplayNamesError::OutOfMemory);
    }
  }
  return Ok();
}

Span<const char> DisplayNames::ToCodeString(Month aMonth) {
  switch (aMonth) {
    case Month::January:
      return MakeStringSpan("1");
    case Month::February:
      return MakeStringSpan("2");
    case Month::March:
      return MakeStringSpan("3");
    case Month::April:
      return MakeStringSpan("4");
    case Month::May:
      return MakeStringSpan("5");
    case Month::June:
      return MakeStringSpan("6");
    case Month::July:
      return MakeStringSpan("7");
    case Month::August:
      return MakeStringSpan("8");
    case Month::September:
      return MakeStringSpan("9");
    case Month::October:
      return MakeStringSpan("10");
    case Month::November:
      return MakeStringSpan("11");
    case Month::December:
      return MakeStringSpan("12");
    case Month::Undecimber:
      return MakeStringSpan("13");
  }
  MOZ_ASSERT_UNREACHABLE();
  return MakeStringSpan("1");
};

}  // namespace mozilla::intl