Bug 1874684 - Part 38: Enable now passing tests. r=allstarschh
[gecko.git] / intl / l10n / FluentBundle.cpp
bloba20fca55649630860a3346f33f6a6aba3ab6b459
1 /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
2 /* vim: set ts=8 sts=2 et sw=2 tw=80: */
3 /* This Source Code Form is subject to the terms of the Mozilla Public
4 * License, v. 2.0. If a copy of the MPL was not distributed with this
5 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
7 #include "FluentBundle.h"
8 #include "nsContentUtils.h"
9 #include "mozilla/dom/ToJSValue.h"
10 #include "mozilla/dom/UnionTypes.h"
11 #include "mozilla/intl/NumberFormat.h"
12 #include "mozilla/intl/DateTimeFormat.h"
13 #include "mozilla/intl/DateTimePatternGenerator.h"
14 #include "nsIInputStream.h"
15 #include "nsStringFwd.h"
16 #include "nsTArray.h"
17 #include "js/PropertyAndElement.h" // JS_DefineElement
19 using namespace mozilla::dom;
21 namespace mozilla {
22 namespace intl {
24 class SizeableUTF8Buffer {
25 public:
26 using CharType = char;
28 bool reserve(size_t size) {
29 mBuffer.reset(reinterpret_cast<CharType*>(malloc(size)));
30 mCapacity = size;
31 return true;
34 CharType* data() { return mBuffer.get(); }
36 size_t capacity() const { return mCapacity; }
38 void written(size_t amount) { mWritten = amount; }
40 size_t mWritten = 0;
41 size_t mCapacity = 0;
43 struct FreePolicy {
44 void operator()(const void* ptr) { free(const_cast<void*>(ptr)); }
47 UniquePtr<CharType[], FreePolicy> mBuffer;
50 NS_IMPL_CYCLE_COLLECTION_WRAPPERCACHE(FluentPattern, mParent)
52 FluentPattern::FluentPattern(nsISupports* aParent, const nsACString& aId)
53 : mId(aId), mParent(aParent) {
54 MOZ_COUNT_CTOR(FluentPattern);
56 FluentPattern::FluentPattern(nsISupports* aParent, const nsACString& aId,
57 const nsACString& aAttrName)
58 : mId(aId), mAttrName(aAttrName), mParent(aParent) {
59 MOZ_COUNT_CTOR(FluentPattern);
62 JSObject* FluentPattern::WrapObject(JSContext* aCx,
63 JS::Handle<JSObject*> aGivenProto) {
64 return FluentPattern_Binding::Wrap(aCx, this, aGivenProto);
67 FluentPattern::~FluentPattern() { MOZ_COUNT_DTOR(FluentPattern); };
69 /* FluentBundle */
71 NS_IMPL_CYCLE_COLLECTION_WRAPPERCACHE(FluentBundle, mParent)
73 FluentBundle::FluentBundle(nsISupports* aParent,
74 UniquePtr<ffi::FluentBundleRc> aRaw)
75 : mParent(aParent), mRaw(std::move(aRaw)) {
76 MOZ_COUNT_CTOR(FluentBundle);
79 already_AddRefed<FluentBundle> FluentBundle::Constructor(
80 const dom::GlobalObject& aGlobal,
81 const UTF8StringOrUTF8StringSequence& aLocales,
82 const dom::FluentBundleOptions& aOptions, ErrorResult& aRv) {
83 nsCOMPtr<nsIGlobalObject> global = do_QueryInterface(aGlobal.GetAsSupports());
84 if (!global) {
85 aRv.Throw(NS_ERROR_FAILURE);
86 return nullptr;
89 bool useIsolating = aOptions.mUseIsolating;
91 nsAutoCString pseudoStrategy;
92 if (aOptions.mPseudoStrategy.WasPassed()) {
93 pseudoStrategy = aOptions.mPseudoStrategy.Value();
96 UniquePtr<ffi::FluentBundleRc> raw;
98 if (aLocales.IsUTF8String()) {
99 const nsACString& locale = aLocales.GetAsUTF8String();
100 raw.reset(
101 ffi::fluent_bundle_new_single(&locale, useIsolating, &pseudoStrategy));
102 } else {
103 const auto& locales = aLocales.GetAsUTF8StringSequence();
104 raw.reset(ffi::fluent_bundle_new(locales.Elements(), locales.Length(),
105 useIsolating, &pseudoStrategy));
108 if (!raw) {
109 aRv.ThrowInvalidStateError(
110 "Failed to create the FluentBundle. Check the "
111 "locales and pseudo strategy arguments.");
112 return nullptr;
115 return do_AddRef(new FluentBundle(global, std::move(raw)));
118 JSObject* FluentBundle::WrapObject(JSContext* aCx,
119 JS::Handle<JSObject*> aGivenProto) {
120 return FluentBundle_Binding::Wrap(aCx, this, aGivenProto);
123 FluentBundle::~FluentBundle() { MOZ_COUNT_DTOR(FluentBundle); };
125 void FluentBundle::GetLocales(nsTArray<nsCString>& aLocales) {
126 fluent_bundle_get_locales(mRaw.get(), &aLocales);
129 void FluentBundle::AddResource(
130 FluentResource& aResource,
131 const dom::FluentBundleAddResourceOptions& aOptions) {
132 bool allowOverrides = aOptions.mAllowOverrides;
133 nsTArray<nsCString> errors;
135 fluent_bundle_add_resource(mRaw.get(), aResource.Raw(), allowOverrides,
136 &errors);
138 for (auto& err : errors) {
139 nsContentUtils::LogSimpleConsoleError(NS_ConvertUTF8toUTF16(err), "L10n"_ns,
140 false, true,
141 nsIScriptError::warningFlag);
145 bool FluentBundle::HasMessage(const nsACString& aId) {
146 return fluent_bundle_has_message(mRaw.get(), &aId);
149 void FluentBundle::GetMessage(const nsACString& aId,
150 Nullable<FluentMessage>& aRetVal) {
151 bool hasValue = false;
152 nsTArray<nsCString> attributes;
153 bool exists =
154 fluent_bundle_get_message(mRaw.get(), &aId, &hasValue, &attributes);
155 if (exists) {
156 FluentMessage& msg = aRetVal.SetValue();
157 if (hasValue) {
158 msg.mValue = new FluentPattern(mParent, aId);
160 for (auto& name : attributes) {
161 auto newEntry = msg.mAttributes.Entries().AppendElement(fallible);
162 newEntry->mKey = name;
163 newEntry->mValue = new FluentPattern(mParent, aId, name);
168 bool extendJSArrayWithErrors(JSContext* aCx, JS::Handle<JSObject*> aErrors,
169 nsTArray<nsCString>& aInput) {
170 uint32_t length;
171 if (NS_WARN_IF(!JS::GetArrayLength(aCx, aErrors, &length))) {
172 return false;
175 for (auto& err : aInput) {
176 JS::Rooted<JS::Value> jsval(aCx);
177 if (!ToJSValue(aCx, NS_ConvertUTF8toUTF16(err), &jsval)) {
178 return false;
180 if (!JS_DefineElement(aCx, aErrors, length++, jsval, JSPROP_ENUMERATE)) {
181 return false;
184 return true;
187 /* static */
188 void FluentBundle::ConvertArgs(const L10nArgs& aArgs,
189 nsTArray<ffi::L10nArg>& aRetVal) {
190 aRetVal.SetCapacity(aArgs.Entries().Length());
191 for (const auto& entry : aArgs.Entries()) {
192 if (!entry.mValue.IsNull()) {
193 const auto& value = entry.mValue.Value();
195 if (value.IsUTF8String()) {
196 aRetVal.AppendElement(ffi::L10nArg{
197 &entry.mKey,
198 ffi::FluentArgument::String(&value.GetAsUTF8String())});
199 } else {
200 aRetVal.AppendElement(ffi::L10nArg{
201 &entry.mKey, ffi::FluentArgument::Double_(value.GetAsDouble())});
207 void FluentBundle::FormatPattern(JSContext* aCx, const FluentPattern& aPattern,
208 const Nullable<L10nArgs>& aArgs,
209 const Optional<JS::Handle<JSObject*>>& aErrors,
210 nsACString& aRetVal, ErrorResult& aRv) {
211 nsTArray<ffi::L10nArg> l10nArgs;
213 if (!aArgs.IsNull()) {
214 const L10nArgs& args = aArgs.Value();
215 ConvertArgs(args, l10nArgs);
218 nsTArray<nsCString> errors;
219 bool succeeded = fluent_bundle_format_pattern(mRaw.get(), &aPattern.mId,
220 &aPattern.mAttrName, &l10nArgs,
221 &aRetVal, &errors);
223 if (!succeeded) {
224 return aRv.ThrowInvalidStateError(
225 "Failed to format the FluentPattern. Likely the "
226 "pattern could not be retrieved from the bundle.");
229 if (aErrors.WasPassed()) {
230 if (!extendJSArrayWithErrors(aCx, aErrors.Value(), errors)) {
231 aRv.ThrowUnknownError("Failed to add errors to an error array.");
236 // FFI
238 extern "C" {
239 ffi::RawNumberFormatter* FluentBuiltInNumberFormatterCreate(
240 const nsCString* aLocale, const ffi::FluentNumberOptionsRaw* aOptions) {
241 NumberFormatOptions options;
242 switch (aOptions->style) {
243 case ffi::FluentNumberStyleRaw::Decimal:
244 break;
245 case ffi::FluentNumberStyleRaw::Currency: {
246 std::string currency = aOptions->currency.get();
247 switch (aOptions->currency_display) {
248 case ffi::FluentNumberCurrencyDisplayStyleRaw::Symbol:
249 options.mCurrency = Some(std::make_pair(
250 currency, NumberFormatOptions::CurrencyDisplay::Symbol));
251 break;
252 case ffi::FluentNumberCurrencyDisplayStyleRaw::Code:
253 options.mCurrency = Some(std::make_pair(
254 currency, NumberFormatOptions::CurrencyDisplay::Code));
255 break;
256 case ffi::FluentNumberCurrencyDisplayStyleRaw::Name:
257 options.mCurrency = Some(std::make_pair(
258 currency, NumberFormatOptions::CurrencyDisplay::Name));
259 break;
260 default:
261 MOZ_ASSERT_UNREACHABLE();
262 break;
264 } break;
265 case ffi::FluentNumberStyleRaw::Percent:
266 options.mPercent = true;
267 break;
268 default:
269 MOZ_ASSERT_UNREACHABLE();
270 break;
273 options.mGrouping = aOptions->use_grouping
274 ? NumberFormatOptions::Grouping::Auto
275 : NumberFormatOptions::Grouping::Never;
276 options.mMinIntegerDigits = Some(aOptions->minimum_integer_digits);
278 if (aOptions->minimum_significant_digits >= 0 ||
279 aOptions->maximum_significant_digits >= 0) {
280 options.mSignificantDigits =
281 Some(std::make_pair(aOptions->minimum_significant_digits,
282 aOptions->maximum_significant_digits));
283 } else {
284 options.mFractionDigits = Some(std::make_pair(
285 aOptions->minimum_fraction_digits, aOptions->maximum_fraction_digits));
288 Result<UniquePtr<NumberFormat>, ICUError> result =
289 NumberFormat::TryCreate(aLocale->get(), options);
291 MOZ_ASSERT(result.isOk());
293 if (result.isOk()) {
294 return reinterpret_cast<ffi::RawNumberFormatter*>(
295 result.unwrap().release());
298 return nullptr;
301 uint8_t* FluentBuiltInNumberFormatterFormat(
302 const ffi::RawNumberFormatter* aFormatter, double input, size_t* aOutCount,
303 size_t* aOutCapacity) {
304 const NumberFormat* nf = reinterpret_cast<const NumberFormat*>(aFormatter);
306 SizeableUTF8Buffer buffer;
307 if (nf->format(input, buffer).isOk()) {
308 *aOutCount = buffer.mWritten;
309 *aOutCapacity = buffer.mCapacity;
310 return reinterpret_cast<uint8_t*>(buffer.mBuffer.release());
313 return nullptr;
316 void FluentBuiltInNumberFormatterDestroy(ffi::RawNumberFormatter* aFormatter) {
317 delete reinterpret_cast<NumberFormat*>(aFormatter);
320 /* DateTime */
322 static Maybe<DateTimeFormat::Style> GetStyle(ffi::FluentDateTimeStyle aStyle) {
323 switch (aStyle) {
324 case ffi::FluentDateTimeStyle::Full:
325 return Some(DateTimeFormat::Style::Full);
326 case ffi::FluentDateTimeStyle::Long:
327 return Some(DateTimeFormat::Style::Long);
328 case ffi::FluentDateTimeStyle::Medium:
329 return Some(DateTimeFormat::Style::Medium);
330 case ffi::FluentDateTimeStyle::Short:
331 return Some(DateTimeFormat::Style::Short);
332 case ffi::FluentDateTimeStyle::None:
333 return Nothing();
335 MOZ_ASSERT_UNREACHABLE();
336 return Nothing();
339 static Maybe<DateTimeFormat::Text> GetText(
340 ffi::FluentDateTimeTextComponent aText) {
341 switch (aText) {
342 case ffi::FluentDateTimeTextComponent::Long:
343 return Some(DateTimeFormat::Text::Long);
344 case ffi::FluentDateTimeTextComponent::Short:
345 return Some(DateTimeFormat::Text::Short);
346 case ffi::FluentDateTimeTextComponent::Narrow:
347 return Some(DateTimeFormat::Text::Narrow);
348 case ffi::FluentDateTimeTextComponent::None:
349 return Nothing();
351 MOZ_ASSERT_UNREACHABLE();
352 return Nothing();
355 static Maybe<DateTimeFormat::Month> GetMonth(
356 ffi::FluentDateTimeMonthComponent aMonth) {
357 switch (aMonth) {
358 case ffi::FluentDateTimeMonthComponent::Numeric:
359 return Some(DateTimeFormat::Month::Numeric);
360 case ffi::FluentDateTimeMonthComponent::TwoDigit:
361 return Some(DateTimeFormat::Month::TwoDigit);
362 case ffi::FluentDateTimeMonthComponent::Long:
363 return Some(DateTimeFormat::Month::Long);
364 case ffi::FluentDateTimeMonthComponent::Short:
365 return Some(DateTimeFormat::Month::Short);
366 case ffi::FluentDateTimeMonthComponent::Narrow:
367 return Some(DateTimeFormat::Month::Narrow);
368 case ffi::FluentDateTimeMonthComponent::None:
369 return Nothing();
371 MOZ_ASSERT_UNREACHABLE();
372 return Nothing();
375 static Maybe<DateTimeFormat::Numeric> GetNumeric(
376 ffi::FluentDateTimeNumericComponent aNumeric) {
377 switch (aNumeric) {
378 case ffi::FluentDateTimeNumericComponent::Numeric:
379 return Some(DateTimeFormat::Numeric::Numeric);
380 case ffi::FluentDateTimeNumericComponent::TwoDigit:
381 return Some(DateTimeFormat::Numeric::TwoDigit);
382 case ffi::FluentDateTimeNumericComponent::None:
383 return Nothing();
385 MOZ_ASSERT_UNREACHABLE();
386 return Nothing();
389 static Maybe<DateTimeFormat::TimeZoneName> GetTimeZoneName(
390 ffi::FluentDateTimeTimeZoneNameComponent aTimeZoneName) {
391 switch (aTimeZoneName) {
392 case ffi::FluentDateTimeTimeZoneNameComponent::Long:
393 return Some(DateTimeFormat::TimeZoneName::Long);
394 case ffi::FluentDateTimeTimeZoneNameComponent::Short:
395 return Some(DateTimeFormat::TimeZoneName::Short);
396 case ffi::FluentDateTimeTimeZoneNameComponent::None:
397 return Nothing();
399 MOZ_ASSERT_UNREACHABLE();
400 return Nothing();
403 static Maybe<DateTimeFormat::HourCycle> GetHourCycle(
404 ffi::FluentDateTimeHourCycle aHourCycle) {
405 switch (aHourCycle) {
406 case ffi::FluentDateTimeHourCycle::H24:
407 return Some(DateTimeFormat::HourCycle::H24);
408 case ffi::FluentDateTimeHourCycle::H23:
409 return Some(DateTimeFormat::HourCycle::H23);
410 case ffi::FluentDateTimeHourCycle::H12:
411 return Some(DateTimeFormat::HourCycle::H12);
412 case ffi::FluentDateTimeHourCycle::H11:
413 return Some(DateTimeFormat::HourCycle::H11);
414 case ffi::FluentDateTimeHourCycle::None:
415 return Nothing();
417 MOZ_ASSERT_UNREACHABLE();
418 return Nothing();
421 static Maybe<DateTimeFormat::ComponentsBag> GetComponentsBag(
422 ffi::FluentDateTimeOptions aOptions) {
423 if (GetStyle(aOptions.date_style) || GetStyle(aOptions.time_style)) {
424 return Nothing();
427 DateTimeFormat::ComponentsBag components;
428 components.era = GetText(aOptions.era);
429 components.year = GetNumeric(aOptions.year);
430 components.month = GetMonth(aOptions.month);
431 components.day = GetNumeric(aOptions.day);
432 components.weekday = GetText(aOptions.weekday);
433 components.hour = GetNumeric(aOptions.hour);
434 components.minute = GetNumeric(aOptions.minute);
435 components.second = GetNumeric(aOptions.second);
436 components.timeZoneName = GetTimeZoneName(aOptions.time_zone_name);
437 components.hourCycle = GetHourCycle(aOptions.hour_cycle);
439 if (!components.era && !components.year && !components.month &&
440 !components.day && !components.weekday && !components.hour &&
441 !components.minute && !components.second && !components.timeZoneName) {
442 return Nothing();
445 return Some(components);
448 ffi::RawDateTimeFormatter* FluentBuiltInDateTimeFormatterCreate(
449 const nsCString* aLocale, ffi::FluentDateTimeOptions aOptions) {
450 auto genResult = DateTimePatternGenerator::TryCreate(aLocale->get());
451 if (genResult.isErr()) {
452 MOZ_ASSERT_UNREACHABLE("There was an error in DateTimeFormat");
453 return nullptr;
455 UniquePtr<DateTimePatternGenerator> dateTimePatternGenerator =
456 genResult.unwrap();
458 if (auto components = GetComponentsBag(aOptions)) {
459 auto result = DateTimeFormat::TryCreateFromComponents(
460 Span(*aLocale), *components, dateTimePatternGenerator.get());
461 if (result.isErr()) {
462 MOZ_ASSERT_UNREACHABLE("There was an error in DateTimeFormat");
463 return nullptr;
466 return reinterpret_cast<ffi::RawDateTimeFormatter*>(
467 result.unwrap().release());
470 DateTimeFormat::StyleBag style;
471 style.date = GetStyle(aOptions.date_style);
472 style.time = GetStyle(aOptions.time_style);
474 auto result = DateTimeFormat::TryCreateFromStyle(
475 Span(*aLocale), style, dateTimePatternGenerator.get());
477 if (result.isErr()) {
478 MOZ_ASSERT_UNREACHABLE("There was an error in DateTimeFormat");
479 return nullptr;
482 return reinterpret_cast<ffi::RawDateTimeFormatter*>(
483 result.unwrap().release());
486 uint8_t* FluentBuiltInDateTimeFormatterFormat(
487 const ffi::RawDateTimeFormatter* aFormatter, double aUnixEpoch,
488 uint32_t* aOutCount) {
489 const auto* dtFormat = reinterpret_cast<const DateTimeFormat*>(aFormatter);
491 SizeableUTF8Buffer buffer;
492 dtFormat->TryFormat(aUnixEpoch, buffer).unwrap();
494 *aOutCount = buffer.mWritten;
496 return reinterpret_cast<uint8_t*>(buffer.mBuffer.release());
499 void FluentBuiltInDateTimeFormatterDestroy(
500 ffi::RawDateTimeFormatter* aFormatter) {
501 delete reinterpret_cast<const DateTimeFormat*>(aFormatter);
505 } // namespace intl
506 } // namespace mozilla