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/UnionTypes.h"
10 #include "mozilla/intl/NumberFormat.h"
11 #include "mozilla/intl/DateTimeFormat.h"
12 #include "mozilla/intl/DateTimePatternGenerator.h"
13 #include "nsIInputStream.h"
14 #include "nsStringFwd.h"
16 #include "js/PropertyAndElement.h" // JS_DefineElement
18 using namespace mozilla::dom
;
23 class SizeableUTF8Buffer
{
25 using CharType
= char;
27 bool reserve(size_t size
) {
28 mBuffer
.reset(reinterpret_cast<CharType
*>(malloc(size
)));
33 CharType
* data() { return mBuffer
.get(); }
35 size_t capacity() const { return mCapacity
; }
37 void written(size_t amount
) { mWritten
= amount
; }
43 void operator()(const void* ptr
) { free(const_cast<void*>(ptr
)); }
46 UniquePtr
<CharType
[], FreePolicy
> mBuffer
;
49 NS_IMPL_CYCLE_COLLECTION_WRAPPERCACHE(FluentPattern
, mParent
)
51 FluentPattern::FluentPattern(nsISupports
* aParent
, const nsACString
& aId
)
52 : mId(aId
), mParent(aParent
) {
53 MOZ_COUNT_CTOR(FluentPattern
);
55 FluentPattern::FluentPattern(nsISupports
* aParent
, const nsACString
& aId
,
56 const nsACString
& aAttrName
)
57 : mId(aId
), mAttrName(aAttrName
), mParent(aParent
) {
58 MOZ_COUNT_CTOR(FluentPattern
);
61 JSObject
* FluentPattern::WrapObject(JSContext
* aCx
,
62 JS::Handle
<JSObject
*> aGivenProto
) {
63 return FluentPattern_Binding::Wrap(aCx
, this, aGivenProto
);
66 FluentPattern::~FluentPattern() { MOZ_COUNT_DTOR(FluentPattern
); };
70 NS_IMPL_CYCLE_COLLECTION_WRAPPERCACHE(FluentBundle
, mParent
)
72 FluentBundle::FluentBundle(nsISupports
* aParent
,
73 UniquePtr
<ffi::FluentBundleRc
> aRaw
)
74 : mParent(aParent
), mRaw(std::move(aRaw
)) {
75 MOZ_COUNT_CTOR(FluentBundle
);
78 already_AddRefed
<FluentBundle
> FluentBundle::Constructor(
79 const dom::GlobalObject
& aGlobal
,
80 const UTF8StringOrUTF8StringSequence
& aLocales
,
81 const dom::FluentBundleOptions
& aOptions
, ErrorResult
& aRv
) {
82 nsCOMPtr
<nsIGlobalObject
> global
= do_QueryInterface(aGlobal
.GetAsSupports());
84 aRv
.Throw(NS_ERROR_FAILURE
);
88 bool useIsolating
= aOptions
.mUseIsolating
;
90 nsAutoCString pseudoStrategy
;
91 if (aOptions
.mPseudoStrategy
.WasPassed()) {
92 pseudoStrategy
= aOptions
.mPseudoStrategy
.Value();
95 UniquePtr
<ffi::FluentBundleRc
> raw
;
97 if (aLocales
.IsUTF8String()) {
98 const nsACString
& locale
= aLocales
.GetAsUTF8String();
100 ffi::fluent_bundle_new_single(&locale
, useIsolating
, &pseudoStrategy
));
102 const auto& locales
= aLocales
.GetAsUTF8StringSequence();
103 raw
.reset(ffi::fluent_bundle_new(locales
.Elements(), locales
.Length(),
104 useIsolating
, &pseudoStrategy
));
108 aRv
.ThrowInvalidStateError(
109 "Failed to create the FluentBundle. Check the "
110 "locales and pseudo strategy arguments.");
114 return do_AddRef(new FluentBundle(global
, std::move(raw
)));
117 JSObject
* FluentBundle::WrapObject(JSContext
* aCx
,
118 JS::Handle
<JSObject
*> aGivenProto
) {
119 return FluentBundle_Binding::Wrap(aCx
, this, aGivenProto
);
122 FluentBundle::~FluentBundle() { MOZ_COUNT_DTOR(FluentBundle
); };
124 void FluentBundle::GetLocales(nsTArray
<nsCString
>& aLocales
) {
125 fluent_bundle_get_locales(mRaw
.get(), &aLocales
);
128 void FluentBundle::AddResource(
129 FluentResource
& aResource
,
130 const dom::FluentBundleAddResourceOptions
& aOptions
) {
131 bool allowOverrides
= aOptions
.mAllowOverrides
;
132 nsTArray
<nsCString
> errors
;
134 fluent_bundle_add_resource(mRaw
.get(), aResource
.Raw(), allowOverrides
,
137 for (auto& err
: errors
) {
138 nsContentUtils::LogSimpleConsoleError(NS_ConvertUTF8toUTF16(err
), "L10n"_ns
,
140 nsIScriptError::warningFlag
);
144 bool FluentBundle::HasMessage(const nsACString
& aId
) {
145 return fluent_bundle_has_message(mRaw
.get(), &aId
);
148 void FluentBundle::GetMessage(const nsACString
& aId
,
149 Nullable
<FluentMessage
>& aRetVal
) {
150 bool hasValue
= false;
151 nsTArray
<nsCString
> attributes
;
153 fluent_bundle_get_message(mRaw
.get(), &aId
, &hasValue
, &attributes
);
155 FluentMessage
& msg
= aRetVal
.SetValue();
157 msg
.mValue
= new FluentPattern(mParent
, aId
);
159 for (auto& name
: attributes
) {
160 auto newEntry
= msg
.mAttributes
.Entries().AppendElement(fallible
);
161 newEntry
->mKey
= name
;
162 newEntry
->mValue
= new FluentPattern(mParent
, aId
, name
);
167 bool extendJSArrayWithErrors(JSContext
* aCx
, JS::Handle
<JSObject
*> aErrors
,
168 nsTArray
<nsCString
>& aInput
) {
170 if (NS_WARN_IF(!JS::GetArrayLength(aCx
, aErrors
, &length
))) {
174 for (auto& err
: aInput
) {
175 JS::Rooted
<JS::Value
> jsval(aCx
);
176 if (!ToJSValue(aCx
, NS_ConvertUTF8toUTF16(err
), &jsval
)) {
179 if (!JS_DefineElement(aCx
, aErrors
, length
++, jsval
, JSPROP_ENUMERATE
)) {
187 void FluentBundle::ConvertArgs(const L10nArgs
& aArgs
,
188 nsTArray
<ffi::L10nArg
>& aRetVal
) {
189 aRetVal
.SetCapacity(aArgs
.Entries().Length());
190 for (const auto& entry
: aArgs
.Entries()) {
191 if (!entry
.mValue
.IsNull()) {
192 const auto& value
= entry
.mValue
.Value();
194 if (value
.IsUTF8String()) {
195 aRetVal
.AppendElement(ffi::L10nArg
{
197 ffi::FluentArgument::String(&value
.GetAsUTF8String())});
199 aRetVal
.AppendElement(ffi::L10nArg
{
200 &entry
.mKey
, ffi::FluentArgument::Double_(value
.GetAsDouble())});
206 void FluentBundle::FormatPattern(JSContext
* aCx
, const FluentPattern
& aPattern
,
207 const Nullable
<L10nArgs
>& aArgs
,
208 const Optional
<JS::Handle
<JSObject
*>>& aErrors
,
209 nsACString
& aRetVal
, ErrorResult
& aRv
) {
210 nsTArray
<ffi::L10nArg
> l10nArgs
;
212 if (!aArgs
.IsNull()) {
213 const L10nArgs
& args
= aArgs
.Value();
214 ConvertArgs(args
, l10nArgs
);
217 nsTArray
<nsCString
> errors
;
218 bool succeeded
= fluent_bundle_format_pattern(mRaw
.get(), &aPattern
.mId
,
219 &aPattern
.mAttrName
, &l10nArgs
,
223 return aRv
.ThrowInvalidStateError(
224 "Failed to format the FluentPattern. Likely the "
225 "pattern could not be retrieved from the bundle.");
228 if (aErrors
.WasPassed()) {
229 if (!extendJSArrayWithErrors(aCx
, aErrors
.Value(), errors
)) {
230 aRv
.ThrowUnknownError("Failed to add errors to an error array.");
238 ffi::RawNumberFormatter
* FluentBuiltInNumberFormatterCreate(
239 const nsCString
* aLocale
, const ffi::FluentNumberOptionsRaw
* aOptions
) {
240 NumberFormatOptions options
;
241 switch (aOptions
->style
) {
242 case ffi::FluentNumberStyleRaw::Decimal
:
244 case ffi::FluentNumberStyleRaw::Currency
: {
245 std::string currency
= aOptions
->currency
.get();
246 switch (aOptions
->currency_display
) {
247 case ffi::FluentNumberCurrencyDisplayStyleRaw::Symbol
:
248 options
.mCurrency
= Some(std::make_pair(
249 currency
, NumberFormatOptions::CurrencyDisplay::Symbol
));
251 case ffi::FluentNumberCurrencyDisplayStyleRaw::Code
:
252 options
.mCurrency
= Some(std::make_pair(
253 currency
, NumberFormatOptions::CurrencyDisplay::Code
));
255 case ffi::FluentNumberCurrencyDisplayStyleRaw::Name
:
256 options
.mCurrency
= Some(std::make_pair(
257 currency
, NumberFormatOptions::CurrencyDisplay::Name
));
260 MOZ_ASSERT_UNREACHABLE();
264 case ffi::FluentNumberStyleRaw::Percent
:
265 options
.mPercent
= true;
268 MOZ_ASSERT_UNREACHABLE();
272 options
.mGrouping
= aOptions
->use_grouping
273 ? NumberFormatOptions::Grouping::Auto
274 : NumberFormatOptions::Grouping::Never
;
275 options
.mMinIntegerDigits
= Some(aOptions
->minimum_integer_digits
);
277 if (aOptions
->minimum_significant_digits
>= 0 ||
278 aOptions
->maximum_significant_digits
>= 0) {
279 options
.mSignificantDigits
=
280 Some(std::make_pair(aOptions
->minimum_significant_digits
,
281 aOptions
->maximum_significant_digits
));
283 options
.mFractionDigits
= Some(std::make_pair(
284 aOptions
->minimum_fraction_digits
, aOptions
->maximum_fraction_digits
));
287 Result
<UniquePtr
<NumberFormat
>, ICUError
> result
=
288 NumberFormat::TryCreate(aLocale
->get(), options
);
290 MOZ_ASSERT(result
.isOk());
293 return reinterpret_cast<ffi::RawNumberFormatter
*>(
294 result
.unwrap().release());
300 uint8_t* FluentBuiltInNumberFormatterFormat(
301 const ffi::RawNumberFormatter
* aFormatter
, double input
, size_t* aOutCount
,
302 size_t* aOutCapacity
) {
303 const NumberFormat
* nf
= reinterpret_cast<const NumberFormat
*>(aFormatter
);
305 SizeableUTF8Buffer buffer
;
306 if (nf
->format(input
, buffer
).isOk()) {
307 *aOutCount
= buffer
.mWritten
;
308 *aOutCapacity
= buffer
.mCapacity
;
309 return reinterpret_cast<uint8_t*>(buffer
.mBuffer
.release());
315 void FluentBuiltInNumberFormatterDestroy(ffi::RawNumberFormatter
* aFormatter
) {
316 delete reinterpret_cast<NumberFormat
*>(aFormatter
);
321 static Maybe
<DateTimeFormat::Style
> GetStyle(ffi::FluentDateTimeStyle aStyle
) {
323 case ffi::FluentDateTimeStyle::Full
:
324 return Some(DateTimeFormat::Style::Full
);
325 case ffi::FluentDateTimeStyle::Long
:
326 return Some(DateTimeFormat::Style::Long
);
327 case ffi::FluentDateTimeStyle::Medium
:
328 return Some(DateTimeFormat::Style::Medium
);
329 case ffi::FluentDateTimeStyle::Short
:
330 return Some(DateTimeFormat::Style::Short
);
331 case ffi::FluentDateTimeStyle::None
:
334 MOZ_ASSERT_UNREACHABLE();
338 static Maybe
<DateTimeFormat::Text
> GetText(
339 ffi::FluentDateTimeTextComponent aText
) {
341 case ffi::FluentDateTimeTextComponent::Long
:
342 return Some(DateTimeFormat::Text::Long
);
343 case ffi::FluentDateTimeTextComponent::Short
:
344 return Some(DateTimeFormat::Text::Short
);
345 case ffi::FluentDateTimeTextComponent::Narrow
:
346 return Some(DateTimeFormat::Text::Narrow
);
347 case ffi::FluentDateTimeTextComponent::None
:
350 MOZ_ASSERT_UNREACHABLE();
354 static Maybe
<DateTimeFormat::Month
> GetMonth(
355 ffi::FluentDateTimeMonthComponent aMonth
) {
357 case ffi::FluentDateTimeMonthComponent::Numeric
:
358 return Some(DateTimeFormat::Month::Numeric
);
359 case ffi::FluentDateTimeMonthComponent::TwoDigit
:
360 return Some(DateTimeFormat::Month::TwoDigit
);
361 case ffi::FluentDateTimeMonthComponent::Long
:
362 return Some(DateTimeFormat::Month::Long
);
363 case ffi::FluentDateTimeMonthComponent::Short
:
364 return Some(DateTimeFormat::Month::Short
);
365 case ffi::FluentDateTimeMonthComponent::Narrow
:
366 return Some(DateTimeFormat::Month::Narrow
);
367 case ffi::FluentDateTimeMonthComponent::None
:
370 MOZ_ASSERT_UNREACHABLE();
374 static Maybe
<DateTimeFormat::Numeric
> GetNumeric(
375 ffi::FluentDateTimeNumericComponent aNumeric
) {
377 case ffi::FluentDateTimeNumericComponent::Numeric
:
378 return Some(DateTimeFormat::Numeric::Numeric
);
379 case ffi::FluentDateTimeNumericComponent::TwoDigit
:
380 return Some(DateTimeFormat::Numeric::TwoDigit
);
381 case ffi::FluentDateTimeNumericComponent::None
:
384 MOZ_ASSERT_UNREACHABLE();
388 static Maybe
<DateTimeFormat::TimeZoneName
> GetTimeZoneName(
389 ffi::FluentDateTimeTimeZoneNameComponent aTimeZoneName
) {
390 switch (aTimeZoneName
) {
391 case ffi::FluentDateTimeTimeZoneNameComponent::Long
:
392 return Some(DateTimeFormat::TimeZoneName::Long
);
393 case ffi::FluentDateTimeTimeZoneNameComponent::Short
:
394 return Some(DateTimeFormat::TimeZoneName::Short
);
395 case ffi::FluentDateTimeTimeZoneNameComponent::None
:
398 MOZ_ASSERT_UNREACHABLE();
402 static Maybe
<DateTimeFormat::HourCycle
> GetHourCycle(
403 ffi::FluentDateTimeHourCycle aHourCycle
) {
404 switch (aHourCycle
) {
405 case ffi::FluentDateTimeHourCycle::H24
:
406 return Some(DateTimeFormat::HourCycle::H24
);
407 case ffi::FluentDateTimeHourCycle::H23
:
408 return Some(DateTimeFormat::HourCycle::H23
);
409 case ffi::FluentDateTimeHourCycle::H12
:
410 return Some(DateTimeFormat::HourCycle::H12
);
411 case ffi::FluentDateTimeHourCycle::H11
:
412 return Some(DateTimeFormat::HourCycle::H11
);
413 case ffi::FluentDateTimeHourCycle::None
:
416 MOZ_ASSERT_UNREACHABLE();
420 static Maybe
<DateTimeFormat::ComponentsBag
> GetComponentsBag(
421 ffi::FluentDateTimeOptions aOptions
) {
422 if (GetStyle(aOptions
.date_style
) || GetStyle(aOptions
.time_style
)) {
426 DateTimeFormat::ComponentsBag components
;
427 components
.era
= GetText(aOptions
.era
);
428 components
.year
= GetNumeric(aOptions
.year
);
429 components
.month
= GetMonth(aOptions
.month
);
430 components
.day
= GetNumeric(aOptions
.day
);
431 components
.weekday
= GetText(aOptions
.weekday
);
432 components
.hour
= GetNumeric(aOptions
.hour
);
433 components
.minute
= GetNumeric(aOptions
.minute
);
434 components
.second
= GetNumeric(aOptions
.second
);
435 components
.timeZoneName
= GetTimeZoneName(aOptions
.time_zone_name
);
436 components
.hourCycle
= GetHourCycle(aOptions
.hour_cycle
);
438 if (!components
.era
&& !components
.year
&& !components
.month
&&
439 !components
.day
&& !components
.weekday
&& !components
.hour
&&
440 !components
.minute
&& !components
.second
&& !components
.timeZoneName
) {
444 return Some(components
);
447 ffi::RawDateTimeFormatter
* FluentBuiltInDateTimeFormatterCreate(
448 const nsCString
* aLocale
, ffi::FluentDateTimeOptions aOptions
) {
449 auto genResult
= DateTimePatternGenerator::TryCreate(aLocale
->get());
450 if (genResult
.isErr()) {
451 MOZ_ASSERT_UNREACHABLE("There was an error in DateTimeFormat");
454 UniquePtr
<DateTimePatternGenerator
> dateTimePatternGenerator
=
457 if (auto components
= GetComponentsBag(aOptions
)) {
458 auto result
= DateTimeFormat::TryCreateFromComponents(
459 Span(*aLocale
), *components
, dateTimePatternGenerator
.get());
460 if (result
.isErr()) {
461 MOZ_ASSERT_UNREACHABLE("There was an error in DateTimeFormat");
465 return reinterpret_cast<ffi::RawDateTimeFormatter
*>(
466 result
.unwrap().release());
469 DateTimeFormat::StyleBag style
;
470 style
.date
= GetStyle(aOptions
.date_style
);
471 style
.time
= GetStyle(aOptions
.time_style
);
473 auto result
= DateTimeFormat::TryCreateFromStyle(
474 Span(*aLocale
), style
, dateTimePatternGenerator
.get());
476 if (result
.isErr()) {
477 MOZ_ASSERT_UNREACHABLE("There was an error in DateTimeFormat");
481 return reinterpret_cast<ffi::RawDateTimeFormatter
*>(
482 result
.unwrap().release());
485 uint8_t* FluentBuiltInDateTimeFormatterFormat(
486 const ffi::RawDateTimeFormatter
* aFormatter
, double aUnixEpoch
,
487 uint32_t* aOutCount
) {
488 const auto* dtFormat
= reinterpret_cast<const DateTimeFormat
*>(aFormatter
);
490 SizeableUTF8Buffer buffer
;
491 dtFormat
->TryFormat(aUnixEpoch
, buffer
).unwrap();
493 *aOutCount
= buffer
.mWritten
;
495 return reinterpret_cast<uint8_t*>(buffer
.mBuffer
.release());
498 void FluentBuiltInDateTimeFormatterDestroy(
499 ffi::RawDateTimeFormatter
* aFormatter
) {
500 delete reinterpret_cast<const DateTimeFormat
*>(aFormatter
);
505 } // namespace mozilla