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 NS_IMPL_CYCLE_COLLECTION_ROOT_NATIVE(FluentPattern
, AddRef
)
52 NS_IMPL_CYCLE_COLLECTION_UNROOT_NATIVE(FluentPattern
, Release
)
54 FluentPattern::FluentPattern(nsISupports
* aParent
, const nsACString
& aId
)
55 : mId(aId
), mParent(aParent
) {
56 MOZ_COUNT_CTOR(FluentPattern
);
58 FluentPattern::FluentPattern(nsISupports
* aParent
, const nsACString
& aId
,
59 const nsACString
& aAttrName
)
60 : mId(aId
), mAttrName(aAttrName
), mParent(aParent
) {
61 MOZ_COUNT_CTOR(FluentPattern
);
64 JSObject
* FluentPattern::WrapObject(JSContext
* aCx
,
65 JS::Handle
<JSObject
*> aGivenProto
) {
66 return FluentPattern_Binding::Wrap(aCx
, this, aGivenProto
);
69 FluentPattern::~FluentPattern() { MOZ_COUNT_DTOR(FluentPattern
); };
73 NS_IMPL_CYCLE_COLLECTION_WRAPPERCACHE(FluentBundle
, mParent
)
75 NS_IMPL_CYCLE_COLLECTION_ROOT_NATIVE(FluentBundle
, AddRef
)
76 NS_IMPL_CYCLE_COLLECTION_UNROOT_NATIVE(FluentBundle
, Release
)
78 FluentBundle::FluentBundle(nsISupports
* aParent
,
79 UniquePtr
<ffi::FluentBundleRc
> aRaw
)
80 : mParent(aParent
), mRaw(std::move(aRaw
)) {
81 MOZ_COUNT_CTOR(FluentBundle
);
84 already_AddRefed
<FluentBundle
> FluentBundle::Constructor(
85 const dom::GlobalObject
& aGlobal
,
86 const UTF8StringOrUTF8StringSequence
& aLocales
,
87 const dom::FluentBundleOptions
& aOptions
, ErrorResult
& aRv
) {
88 nsCOMPtr
<nsIGlobalObject
> global
= do_QueryInterface(aGlobal
.GetAsSupports());
90 aRv
.Throw(NS_ERROR_FAILURE
);
94 bool useIsolating
= aOptions
.mUseIsolating
;
96 nsAutoCString pseudoStrategy
;
97 if (aOptions
.mPseudoStrategy
.WasPassed()) {
98 pseudoStrategy
= aOptions
.mPseudoStrategy
.Value();
101 UniquePtr
<ffi::FluentBundleRc
> raw
;
103 if (aLocales
.IsUTF8String()) {
104 const nsACString
& locale
= aLocales
.GetAsUTF8String();
106 ffi::fluent_bundle_new_single(&locale
, useIsolating
, &pseudoStrategy
));
108 const auto& locales
= aLocales
.GetAsUTF8StringSequence();
109 raw
.reset(ffi::fluent_bundle_new(locales
.Elements(), locales
.Length(),
110 useIsolating
, &pseudoStrategy
));
114 aRv
.ThrowInvalidStateError(
115 "Failed to create the FluentBundle. Check the "
116 "locales and pseudo strategy arguments.");
120 return do_AddRef(new FluentBundle(global
, std::move(raw
)));
123 JSObject
* FluentBundle::WrapObject(JSContext
* aCx
,
124 JS::Handle
<JSObject
*> aGivenProto
) {
125 return FluentBundle_Binding::Wrap(aCx
, this, aGivenProto
);
128 FluentBundle::~FluentBundle() { MOZ_COUNT_DTOR(FluentBundle
); };
130 void FluentBundle::GetLocales(nsTArray
<nsCString
>& aLocales
) {
131 fluent_bundle_get_locales(mRaw
.get(), &aLocales
);
134 void FluentBundle::AddResource(
135 FluentResource
& aResource
,
136 const dom::FluentBundleAddResourceOptions
& aOptions
) {
137 bool allowOverrides
= aOptions
.mAllowOverrides
;
138 nsTArray
<nsCString
> errors
;
140 fluent_bundle_add_resource(mRaw
.get(), aResource
.Raw(), allowOverrides
,
143 for (auto& err
: errors
) {
144 nsContentUtils::LogSimpleConsoleError(NS_ConvertUTF8toUTF16(err
), "L10n",
146 nsIScriptError::warningFlag
);
150 bool FluentBundle::HasMessage(const nsACString
& aId
) {
151 return fluent_bundle_has_message(mRaw
.get(), &aId
);
154 void FluentBundle::GetMessage(const nsACString
& aId
,
155 Nullable
<FluentMessage
>& aRetVal
) {
156 bool hasValue
= false;
157 nsTArray
<nsCString
> attributes
;
159 fluent_bundle_get_message(mRaw
.get(), &aId
, &hasValue
, &attributes
);
161 FluentMessage
& msg
= aRetVal
.SetValue();
163 msg
.mValue
= new FluentPattern(mParent
, aId
);
165 for (auto& name
: attributes
) {
166 auto newEntry
= msg
.mAttributes
.Entries().AppendElement(fallible
);
167 newEntry
->mKey
= name
;
168 newEntry
->mValue
= new FluentPattern(mParent
, aId
, name
);
173 bool extendJSArrayWithErrors(JSContext
* aCx
, JS::Handle
<JSObject
*> aErrors
,
174 nsTArray
<nsCString
>& aInput
) {
176 if (NS_WARN_IF(!JS::GetArrayLength(aCx
, aErrors
, &length
))) {
180 for (auto& err
: aInput
) {
181 JS::Rooted
<JS::Value
> jsval(aCx
);
182 if (!ToJSValue(aCx
, NS_ConvertUTF8toUTF16(err
), &jsval
)) {
185 if (!JS_DefineElement(aCx
, aErrors
, length
++, jsval
, JSPROP_ENUMERATE
)) {
193 void FluentBundle::ConvertArgs(const L10nArgs
& aArgs
,
194 nsTArray
<ffi::L10nArg
>& aRetVal
) {
195 aRetVal
.SetCapacity(aArgs
.Entries().Length());
196 for (const auto& entry
: aArgs
.Entries()) {
197 if (!entry
.mValue
.IsNull()) {
198 const auto& value
= entry
.mValue
.Value();
200 if (value
.IsUTF8String()) {
201 aRetVal
.AppendElement(ffi::L10nArg
{
203 ffi::FluentArgument::String(&value
.GetAsUTF8String())});
205 aRetVal
.AppendElement(ffi::L10nArg
{
206 &entry
.mKey
, ffi::FluentArgument::Double_(value
.GetAsDouble())});
212 void FluentBundle::FormatPattern(JSContext
* aCx
, const FluentPattern
& aPattern
,
213 const Nullable
<L10nArgs
>& aArgs
,
214 const Optional
<JS::Handle
<JSObject
*>>& aErrors
,
215 nsACString
& aRetVal
, ErrorResult
& aRv
) {
216 nsTArray
<ffi::L10nArg
> l10nArgs
;
218 if (!aArgs
.IsNull()) {
219 const L10nArgs
& args
= aArgs
.Value();
220 ConvertArgs(args
, l10nArgs
);
223 nsTArray
<nsCString
> errors
;
224 bool succeeded
= fluent_bundle_format_pattern(mRaw
.get(), &aPattern
.mId
,
225 &aPattern
.mAttrName
, &l10nArgs
,
229 return aRv
.ThrowInvalidStateError(
230 "Failed to format the FluentPattern. Likely the "
231 "pattern could not be retrieved from the bundle.");
234 if (aErrors
.WasPassed()) {
235 if (!extendJSArrayWithErrors(aCx
, aErrors
.Value(), errors
)) {
236 aRv
.ThrowUnknownError("Failed to add errors to an error array.");
244 ffi::RawNumberFormatter
* FluentBuiltInNumberFormatterCreate(
245 const nsCString
* aLocale
, const ffi::FluentNumberOptionsRaw
* aOptions
) {
246 NumberFormatOptions options
;
247 switch (aOptions
->style
) {
248 case ffi::FluentNumberStyleRaw::Decimal
:
250 case ffi::FluentNumberStyleRaw::Currency
: {
251 std::string currency
= aOptions
->currency
.get();
252 switch (aOptions
->currency_display
) {
253 case ffi::FluentNumberCurrencyDisplayStyleRaw::Symbol
:
254 options
.mCurrency
= Some(std::make_pair(
255 currency
, NumberFormatOptions::CurrencyDisplay::Symbol
));
257 case ffi::FluentNumberCurrencyDisplayStyleRaw::Code
:
258 options
.mCurrency
= Some(std::make_pair(
259 currency
, NumberFormatOptions::CurrencyDisplay::Code
));
261 case ffi::FluentNumberCurrencyDisplayStyleRaw::Name
:
262 options
.mCurrency
= Some(std::make_pair(
263 currency
, NumberFormatOptions::CurrencyDisplay::Name
));
266 MOZ_ASSERT_UNREACHABLE();
270 case ffi::FluentNumberStyleRaw::Percent
:
271 options
.mPercent
= true;
274 MOZ_ASSERT_UNREACHABLE();
278 options
.mGrouping
= aOptions
->use_grouping
279 ? NumberFormatOptions::Grouping::Auto
280 : NumberFormatOptions::Grouping::Never
;
281 options
.mMinIntegerDigits
= Some(aOptions
->minimum_integer_digits
);
283 if (aOptions
->minimum_significant_digits
>= 0 ||
284 aOptions
->maximum_significant_digits
>= 0) {
285 options
.mSignificantDigits
=
286 Some(std::make_pair(aOptions
->minimum_significant_digits
,
287 aOptions
->maximum_significant_digits
));
289 options
.mFractionDigits
= Some(std::make_pair(
290 aOptions
->minimum_fraction_digits
, aOptions
->maximum_fraction_digits
));
293 Result
<UniquePtr
<NumberFormat
>, ICUError
> result
=
294 NumberFormat::TryCreate(aLocale
->get(), options
);
296 MOZ_ASSERT(result
.isOk());
299 return reinterpret_cast<ffi::RawNumberFormatter
*>(
300 result
.unwrap().release());
306 uint8_t* FluentBuiltInNumberFormatterFormat(
307 const ffi::RawNumberFormatter
* aFormatter
, double input
, size_t* aOutCount
,
308 size_t* aOutCapacity
) {
309 const NumberFormat
* nf
= reinterpret_cast<const NumberFormat
*>(aFormatter
);
311 SizeableUTF8Buffer buffer
;
312 if (nf
->format(input
, buffer
).isOk()) {
313 *aOutCount
= buffer
.mWritten
;
314 *aOutCapacity
= buffer
.mCapacity
;
315 return reinterpret_cast<uint8_t*>(buffer
.mBuffer
.release());
321 void FluentBuiltInNumberFormatterDestroy(ffi::RawNumberFormatter
* aFormatter
) {
322 delete reinterpret_cast<NumberFormat
*>(aFormatter
);
327 static Maybe
<DateTimeFormat::Style
> GetStyle(ffi::FluentDateTimeStyle aStyle
) {
329 case ffi::FluentDateTimeStyle::Full
:
330 return Some(DateTimeFormat::Style::Full
);
331 case ffi::FluentDateTimeStyle::Long
:
332 return Some(DateTimeFormat::Style::Long
);
333 case ffi::FluentDateTimeStyle::Medium
:
334 return Some(DateTimeFormat::Style::Medium
);
335 case ffi::FluentDateTimeStyle::Short
:
336 return Some(DateTimeFormat::Style::Short
);
337 case ffi::FluentDateTimeStyle::None
:
340 MOZ_ASSERT_UNREACHABLE();
344 ffi::RawDateTimeFormatter
* FluentBuiltInDateTimeFormatterCreate(
345 const nsCString
* aLocale
, const ffi::FluentDateTimeOptionsRaw
* aOptions
) {
346 auto genResult
= DateTimePatternGenerator::TryCreate(aLocale
->get());
347 if (genResult
.isErr()) {
348 MOZ_ASSERT_UNREACHABLE("There was an error in DateTimeFormat");
351 UniquePtr
<DateTimePatternGenerator
> dateTimePatternGenerator
=
354 if (aOptions
->date_style
== ffi::FluentDateTimeStyle::None
&&
355 aOptions
->time_style
== ffi::FluentDateTimeStyle::None
&&
356 !aOptions
->skeleton
.IsEmpty()) {
357 auto result
= DateTimeFormat::TryCreateFromSkeleton(
358 Span(aLocale
->get(), aLocale
->Length()),
359 Span(aOptions
->skeleton
.get(), aOptions
->skeleton
.Length()),
360 dateTimePatternGenerator
.get(), Nothing());
361 if (result
.isErr()) {
362 MOZ_ASSERT_UNREACHABLE("There was an error in DateTimeFormat");
366 return reinterpret_cast<ffi::RawDateTimeFormatter
*>(
367 result
.unwrap().release());
370 DateTimeFormat::StyleBag style
;
371 style
.date
= GetStyle(aOptions
->date_style
);
372 style
.time
= GetStyle(aOptions
->time_style
);
374 auto result
= DateTimeFormat::TryCreateFromStyle(
375 Span(aLocale
->get(), aLocale
->Length()), style
,
376 dateTimePatternGenerator
.get());
378 if (result
.isErr()) {
379 MOZ_ASSERT_UNREACHABLE("There was an error in DateTimeFormat");
383 return reinterpret_cast<ffi::RawDateTimeFormatter
*>(
384 result
.unwrap().release());
387 uint8_t* FluentBuiltInDateTimeFormatterFormat(
388 const ffi::RawDateTimeFormatter
* aFormatter
, double aUnixEpoch
,
389 uint32_t* aOutCount
) {
390 const auto* dtFormat
= reinterpret_cast<const DateTimeFormat
*>(aFormatter
);
392 SizeableUTF8Buffer buffer
;
393 dtFormat
->TryFormat(aUnixEpoch
, buffer
).unwrap();
395 *aOutCount
= buffer
.mWritten
;
397 return reinterpret_cast<uint8_t*>(buffer
.mBuffer
.release());
400 void FluentBuiltInDateTimeFormatterDestroy(
401 ffi::RawDateTimeFormatter
* aFormatter
) {
402 delete reinterpret_cast<const DateTimeFormat
*>(aFormatter
);
407 } // namespace mozilla