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"
17 #include "js/PropertyAndElement.h" // JS_DefineElement
19 using namespace mozilla::dom
;
24 class SizeableUTF8Buffer
{
26 using CharType
= char;
28 bool reserve(size_t size
) {
29 mBuffer
.reset(reinterpret_cast<CharType
*>(malloc(size
)));
34 CharType
* data() { return mBuffer
.get(); }
36 size_t capacity() const { return mCapacity
; }
38 void written(size_t amount
) { mWritten
= amount
; }
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
); };
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());
85 aRv
.Throw(NS_ERROR_FAILURE
);
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();
101 ffi::fluent_bundle_new_single(&locale
, useIsolating
, &pseudoStrategy
));
103 const auto& locales
= aLocales
.GetAsUTF8StringSequence();
104 raw
.reset(ffi::fluent_bundle_new(locales
.Elements(), locales
.Length(),
105 useIsolating
, &pseudoStrategy
));
109 aRv
.ThrowInvalidStateError(
110 "Failed to create the FluentBundle. Check the "
111 "locales and pseudo strategy arguments.");
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
,
138 for (auto& err
: errors
) {
139 nsContentUtils::LogSimpleConsoleError(NS_ConvertUTF8toUTF16(err
), "L10n"_ns
,
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
;
154 fluent_bundle_get_message(mRaw
.get(), &aId
, &hasValue
, &attributes
);
156 FluentMessage
& msg
= aRetVal
.SetValue();
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
) {
171 if (NS_WARN_IF(!JS::GetArrayLength(aCx
, aErrors
, &length
))) {
175 for (auto& err
: aInput
) {
176 JS::Rooted
<JS::Value
> jsval(aCx
);
177 if (!ToJSValue(aCx
, NS_ConvertUTF8toUTF16(err
), &jsval
)) {
180 if (!JS_DefineElement(aCx
, aErrors
, length
++, jsval
, JSPROP_ENUMERATE
)) {
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
{
198 ffi::FluentArgument::String(&value
.GetAsUTF8String())});
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
,
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.");
239 ffi::RawNumberFormatter
* FluentBuiltInNumberFormatterCreate(
240 const nsCString
* aLocale
, const ffi::FluentNumberOptionsRaw
* aOptions
) {
241 NumberFormatOptions options
;
242 switch (aOptions
->style
) {
243 case ffi::FluentNumberStyleRaw::Decimal
:
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
));
252 case ffi::FluentNumberCurrencyDisplayStyleRaw::Code
:
253 options
.mCurrency
= Some(std::make_pair(
254 currency
, NumberFormatOptions::CurrencyDisplay::Code
));
256 case ffi::FluentNumberCurrencyDisplayStyleRaw::Name
:
257 options
.mCurrency
= Some(std::make_pair(
258 currency
, NumberFormatOptions::CurrencyDisplay::Name
));
261 MOZ_ASSERT_UNREACHABLE();
265 case ffi::FluentNumberStyleRaw::Percent
:
266 options
.mPercent
= true;
269 MOZ_ASSERT_UNREACHABLE();
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
));
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());
294 return reinterpret_cast<ffi::RawNumberFormatter
*>(
295 result
.unwrap().release());
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());
316 void FluentBuiltInNumberFormatterDestroy(ffi::RawNumberFormatter
* aFormatter
) {
317 delete reinterpret_cast<NumberFormat
*>(aFormatter
);
322 static Maybe
<DateTimeFormat::Style
> GetStyle(ffi::FluentDateTimeStyle 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
:
335 MOZ_ASSERT_UNREACHABLE();
339 static Maybe
<DateTimeFormat::Text
> GetText(
340 ffi::FluentDateTimeTextComponent 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
:
351 MOZ_ASSERT_UNREACHABLE();
355 static Maybe
<DateTimeFormat::Month
> GetMonth(
356 ffi::FluentDateTimeMonthComponent 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
:
371 MOZ_ASSERT_UNREACHABLE();
375 static Maybe
<DateTimeFormat::Numeric
> GetNumeric(
376 ffi::FluentDateTimeNumericComponent 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
:
385 MOZ_ASSERT_UNREACHABLE();
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
:
399 MOZ_ASSERT_UNREACHABLE();
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
:
417 MOZ_ASSERT_UNREACHABLE();
421 static Maybe
<DateTimeFormat::ComponentsBag
> GetComponentsBag(
422 ffi::FluentDateTimeOptions aOptions
) {
423 if (GetStyle(aOptions
.date_style
) || GetStyle(aOptions
.time_style
)) {
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
) {
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");
455 UniquePtr
<DateTimePatternGenerator
> dateTimePatternGenerator
=
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");
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");
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
);
506 } // namespace mozilla