Huge News!Announcing our $40M Series B led by Abstract Ventures.Learn More
Socket
Sign inDemoInstall
Socket

@fluent/bundle

Package Overview
Dependencies
Maintainers
4
Versions
10
Alerts
File Explorer

Advanced tools

Socket logo

Install Socket

Detect and block malicious and high-risk dependencies

Install

@fluent/bundle - npm Package Compare versions

Comparing version 0.17.1 to 0.18.0

867

CHANGELOG.md
# Changelog
## [0.18.0](https://github.com/projectfluent/fluent.js/compare/@fluent/bundle@0.17.1...@fluent/bundle@0.18.0) (2023-03-13)
- Drop Node.js v12 support, add v18 & latest to CI tests
([#607](https://github.com/projectfluent/fluent.js/pull/607))
- Use `/** comments */` where appropriate
## [@fluent/bundle 0.17.1](https://github.com/projectfluent/fluent.js/compare/@fluent/bundle@0.17.0...@fluent/bundle@0.17.1) (2021-12-21)
- Add per locale Intl memoizer
([#504](https://github.com/projectfluent/fluent.js/pull/504))
- The type of caught errors is validated at runtime
([#575](https://github.com/projectfluent/fluent.js/pull/575))
- Add per locale Intl memoizer
([#504](https://github.com/projectfluent/fluent.js/pull/504))
- The type of caught errors is validated at runtime
([#575](https://github.com/projectfluent/fluent.js/pull/575))
## @fluent/bundle 0.17.0 (September 13, 2021)
- Remove `"type": "commonjs"` from the package's root `package.json`, but add
`"type": "module"` to the `esm/` directory.
([#556](https://github.com/projectfluent/fluent.js/pull/556),
[#567](https://github.com/projectfluent/fluent.js/pull/567))
- Set Node.js 12 as the minimum supported version ([#557](https://github.com/projectfluent/fluent.js/pull/557))
- Remove `"type": "commonjs"` from the package's root `package.json`, but add
`"type": "module"` to the `esm/` directory.
([#556](https://github.com/projectfluent/fluent.js/pull/556),
[#567](https://github.com/projectfluent/fluent.js/pull/567))
- Set Node.js 12 as the minimum supported version ([#557](https://github.com/projectfluent/fluent.js/pull/557))
## @fluent/bundle 0.16.1 (April 9, 2021)
- Separate FluentNumber/Datetime opts when used in DATETIME/NUMBER. (#526)
- Separate FluentNumber/Datetime opts when used in DATETIME/NUMBER. (#526)
## @fluent/bundle 0.16.0 (July 1, 2020)
- Rename `FluentArgument` to `FluentVariable`. (#499)
- Rename `FluentArgument` to `FluentVariable`. (#499)
`FluentVariable` is a TypeScript-only type definition used to describe
value types which can be passed as variables to translations.
`FluentVariable` is a TypeScript-only type definition used to describe
value types which can be passed as variables to translations.
- Remove `compat.js` builds and compile everything to ES2018. (#472)
- Remove `compat.js` builds and compile everything to ES2018. (#472)
TypeScript source code is now compiled to ES2018 files in the `esm/`
directory. These files are then bundled into a single `index.js` UMD file
without any further transpilation.
TypeScript source code is now compiled to ES2018 files in the `esm/`
directory. These files are then bundled into a single `index.js` UMD file
without any further transpilation.
The `compat.js` build (available as `@fluent/bundle/compat`) was removed.
Please use your own transpilation pipeline if ES2018 is too recent for
your project.
The `compat.js` build (available as `@fluent/bundle/compat`) was removed.
Please use your own transpilation pipeline if ES2018 is too recent for
your project.
Refer to https://github.com/projectfluent/fluent.js/wiki/Compatibility
for more information.
Refer to https://github.com/projectfluent/fluent.js/wiki/Compatibility
for more information.
## @fluent/bundle 0.15.1 (April 7, 2020)
- Allow only some formatting options to `NUMBER` and `DATETIME`. (#464)
- Allow only some formatting options to `NUMBER` and `DATETIME`. (#464)
The builtin functions available to translations were liberal in terms of
the options they accepted. This could lead to undesired results, e.g.
when for the same number value, a translation would specify a different
currency than the source language.
The builtin functions available to translations were liberal in terms of
the options they accepted. This could lead to undesired results, e.g.
when for the same number value, a translation would specify a different
currency than the source language.
The `NUMBER` builtin now only recognizes the following options:
The `NUMBER` builtin now only recognizes the following options:
unitDisplay
currencyDisplay
useGrouping
minimumIntegerDigits
minimumFractionDigits
maximumFractionDigits
minimumSignificantDigits
maximumSignificantDigits
unitDisplay
currencyDisplay
useGrouping
minimumIntegerDigits
minimumFractionDigits
maximumFractionDigits
minimumSignificantDigits
maximumSignificantDigits
The `DATETIME` builtin now only recognizes the following options:
The `DATETIME` builtin now only recognizes the following options:
dateStyle
timeStyle
fractionalSecondDigits
dayPeriod
hour12
weekday
era
year
month
day
hour
minute
second
timeZoneName
dateStyle
timeStyle
fractionalSecondDigits
dayPeriod
hour12
weekday
era
year
month
day
hour
minute
second
timeZoneName
All other options are ignored.
All other options are ignored.
## @fluent/bundle 0.15.0 (January 23, 2020)
- Migrate to TypeScript. (#436)
- Migrate to TypeScript. (#436)
The source code of `@fluent/bundle` has been ported to TypeScript. There
are no breaking changes to the public API.
The source code of `@fluent/bundle` has been ported to TypeScript. There
are no breaking changes to the public API.
A new export has been added for TS consumers: `FluentValue` which is a
union of the `FluentType` class and `string`. This reflects the
implementation detail of `@fluent/bundle` which treats primitive strings
as valid `FluentType` instances.
A new export has been added for TS consumers: `FluentValue` which is a
union of the `FluentType` class and `string`. This reflects the
implementation detail of `@fluent/bundle` which treats primitive strings
as valid `FluentType` instances.
The ES module files are now published into the `esm` directory. It also
contains the type definitions for TS consumers. The `src` directory has
been removed from the published packages.
The ES module files are now published into the `esm` directory. It also
contains the type definitions for TS consumers. The `src` directory has
been removed from the published packages.

@@ -102,6 +108,6 @@ ## @fluent/bundle 0.14.1 (December 20, 2019)

Expressions which resolved to strings over 2500 characters long used to be
considered dangerous. This is no longer the case. Instead, there's a limit
on how many placeable can be resolved during a single call to
`formatPattern`, to protect from high CPU usage in deeply nested patterns.
Expressions which resolved to strings over 2500 characters long used to be
considered dangerous. This is no longer the case. Instead, there's a limit
on how many placeable can be resolved during a single call to
`formatPattern`, to protect from high CPU usage in deeply nested patterns.

@@ -115,134 +121,133 @@ - Fix a bug which made it impossible to pass a variable called `hasOwnProperty`

- Remove `FluentBundle.addMessages`.
- Remove `FluentBundle.addMessages`.
Use `FluentBundle.addResource` instead, combined with a `FluentResource`
instance.
Use `FluentBundle.addResource` instead, combined with a `FluentResource`
instance.
- Remove `FluentBundle.messages`.
- Remove `FluentBundle.messages`.
The list of messages in the bundle should not be inspected. If you need
to do it, please use the tooling parser from `@fluent/syntax`.
The list of messages in the bundle should not be inspected. If you need
to do it, please use the tooling parser from `@fluent/syntax`.
- Change the shape returned by `FluentBundle.getMessage`.
- Change the shape returned by `FluentBundle.getMessage`.
The method now returns the following shape:
The method now returns the following shape:
{value: Pattern | null, attributes: Record<string, Pattern>}
{value: Pattern | null, attributes: Record<string, Pattern>}
- The internal representtion of `Pattern` is private.
- The internal representtion of `Pattern` is private.
Raw messages returned from `getMessage` have their values and attributes
stored in a `Pattern` type. The internal representation of this type is
private and implementation-specific. It should not be inspected nor used
for any purposes. The implementation may change without a warning in
future releases. `Patterns` are black boxes and are meant to be used as
arguments to `formatPattern`.
Raw messages returned from `getMessage` have their values and attributes
stored in a `Pattern` type. The internal representation of this type is
private and implementation-specific. It should not be inspected nor used
for any purposes. The implementation may change without a warning in
future releases. `Patterns` are black boxes and are meant to be used as
arguments to `formatPattern`.
- Rename `FluentBundle.format` to `formatPattern`.
- Rename `FluentBundle.format` to `formatPattern`.
`formatPattern` only accepts valid `Patterns` as the first argument. In
practice, you'll want to first retrieve a raw message from the bundle via
`getMessage`, and then format the value (if present) and the attributes
separately.
`formatPattern` only accepts valid `Patterns` as the first argument. In
practice, you'll want to first retrieve a raw message from the bundle via
`getMessage`, and then format the value (if present) and the attributes
separately.
```js
let message = bundle.getMessage("hello");
if (message.value) {
bundle.formatPattern(message.value, {userName: "Alex"});
}
```
```js
let message = bundle.getMessage("hello");
if (message.value) {
bundle.formatPattern(message.value, { userName: "Alex" });
}
```
The list of all attributes defined in the message can be obtained with
`Object.keys(message.attributes)`.
The list of all attributes defined in the message can be obtained with
`Object.keys(message.attributes)`.
- Throw from `formatPattern` when `errors` are not passed as an argument.
- Throw from `formatPattern` when `errors` are not passed as an argument.
The old `format()` method would silence all errors if the thrid argument,
the `errors` array was not given. `formatPattern` changes this behavior
to throwing on the first encountered error and interrupting the
formatting.
The old `format()` method would silence all errors if the thrid argument,
the `errors` array was not given. `formatPattern` changes this behavior
to throwing on the first encountered error and interrupting the
formatting.
```js
try {
bundle.formatPattern(message.value, args);
} catch (err) {
// Handle the error yourself.
}
```
```js
try {
bundle.formatPattern(message.value, args);
} catch (err) {
// Handle the error yourself.
}
```
It's still possible to pass the `errors` array as the third argument to
`formatPattern`. All errors encountered during the formatting will be
then appended to the array. In this scenario, `formatPattern` is
guaranteed to never throw because of errors in the translation.
It's still possible to pass the `errors` array as the third argument to
`formatPattern`. All errors encountered during the formatting will be
then appended to the array. In this scenario, `formatPattern` is
guaranteed to never throw because of errors in the translation.
```js
let errorrs = [];
bundle.formatPattern(message.value, args, errors);
for (let error of errors) {
// Report errors.
}
```
```js
let errorrs = [];
bundle.formatPattern(message.value, args, errors);
for (let error of errors) {
// Report errors.
}
```
### `FluentResource` API
- Remove the static `FluentResource.fromString` method.
- Remove the static `FluentResource.fromString` method.
Parse resources by using the constructor: `new FluentResource(text)`.
Parse resources by using the constructor: `new FluentResource(text)`.
- Do not extend `Map`.
- Do not extend `Map`.
`FluentResources` are now instances of their own class only.
`FluentResources` are now instances of their own class only.
- Add `FluentResource.body`.
- Add `FluentResource.body`.
The `body` field is an array storing the resource's parsed messages and
terms.
The `body` field is an array storing the resource's parsed messages and
terms.
### `FluentType` API
- `FluentNumber` must be instantiated with a number.
- `FluentNumber` must be instantiated with a number.
The constructor doesn't call `parseFloat` on the passed value anymore.
The constructor doesn't call `parseFloat` on the passed value anymore.
- `FluentDateTime` must be instantiated with a number.
- `FluentDateTime` must be instantiated with a number.
The constructor doesn't call `new Date()` on the passed value anymore.
The date is stored as the numerical timestamp, in milliseconds since the
epoch.
The constructor doesn't call `new Date()` on the passed value anymore.
The date is stored as the numerical timestamp, in milliseconds since the
epoch.
### Formatting Changes
- Report errors from instantiating Intl objects used for formatting.
- Report errors from functions, including built-in functions.
- Format numbers and dates to safe defaults in case or errors. (#410)
- When a transform function is given, transform only `TextElements`.
- Report errors from instantiating Intl objects used for formatting.
- Report errors from functions, including built-in functions.
- Format numbers and dates to safe defaults in case or errors. (#410)
- When a transform function is given, transform only `TextElements`.
## @fluent/bundle 0.13.0 (July 25, 2019)
- Rename `fluent` to `@fluent/bundle`.
- Rename `fluent` to `@fluent/bundle`.
## fluent 0.13.0 (July 25, 2019)
- Support Fluent Syntax 1.0.
- Support Fluent Syntax 1.0.
Syntax 1.0 is the same as Syntax 0.9, and the support for it in this
release continues to be the same as in `fluent` 0.12. This note is meant
to clearly indicate that `fluent` supports the first stable version of
the Syntax specification.
Syntax 1.0 is the same as Syntax 0.9, and the support for it in this
release continues to be the same as in `fluent` 0.12. This note is meant
to clearly indicate that `fluent` supports the first stable version of
the Syntax specification.
- Improve the fallback string in case of expression errors.
- Improve the fallback string in case of expression errors.
Unresolved expressions are now printed with braces around them, e.g.
`{missing}` rather than `missing` (#368). References to missing message
attributes now fall back to `{messageId.attributeName}` (#370).
Unresolved expressions are now printed with braces around them, e.g.
`{missing}` rather than `missing` (#368). References to missing message
attributes now fall back to `{messageId.attributeName}` (#370).
- Remove the `ftl` dedent helper.
- Remove the `ftl` dedent helper.
The `ftl` dedent helper has moved to its own package, `@fluent/dedent`.
Note that its behavior has changed slightly, too. See the
[README][dedent-readme] for details.
The `ftl` dedent helper has moved to its own package, `@fluent/dedent`.
Note that its behavior has changed slightly, too. See the
[README][dedent-readme] for details.
[dedent-readme]: https://www.npmjs.com/package/@fluent/dedent
[dedent-readme]: https://www.npmjs.com/package/@fluent/dedent
## fluent 0.12.0 (March 26, 2019)

@@ -254,15 +259,15 @@

- Implement Fluent Syntax 0.9.
- Implement Fluent Syntax 0.9.
Most of the changes introduced in Syntax 0.9 affect the full tooling AST
and thus do not apply to the `fluent` package which uses a different data
structure for the result of the parsing, optimized for the runtime
performance.
Most of the changes introduced in Syntax 0.9 affect the full tooling AST
and thus do not apply to the `fluent` package which uses a different data
structure for the result of the parsing, optimized for the runtime
performance.
Syntax features deprecated in Syntax 0.8 have been removed in Syntax 0.9.
The support for them have been removed in this release of `fluent` too.
Syntax features deprecated in Syntax 0.8 have been removed in Syntax 0.9.
The support for them have been removed in this release of `fluent` too.
Consult the full Syntax 0.9 [changelog][chlog0.9] for details.
Consult the full Syntax 0.9 [changelog][chlog0.9] for details.
[chlog0.9]: https://github.com/projectfluent/fluent/releases/tag/v0.9.0
[chlog0.9]: https://github.com/projectfluent/fluent/releases/tag/v0.9.0

@@ -273,13 +278,11 @@ ### Backward-incompatible changes:

## fluent 0.11.0 (February 15, 2019)
- Add the `allowOverrides` option to `FluentBundle.addResource`. (#332)
- Add the `allowOverrides` option to `FluentBundle.addResource`. (#332)
`FluentBundle.addResource` and `FluentBundle.addMessages` now both accept
an `options` object as the last argument. The `allowOverrides` option may
be used to control whether it's allowed to override existing mesages or
terms with new values. The default is `false`.
`FluentBundle.addResource` and `FluentBundle.addMessages` now both accept
an `options` object as the last argument. The `allowOverrides` option may
be used to control whether it's allowed to override existing mesages or
terms with new values. The default is `false`.
## fluent 0.10.0 (December 13, 2018)

@@ -291,60 +294,58 @@

- Implement Fluent Syntax 0.8. (#303)
- Implement Fluent Syntax 0.8. (#303)
This is only a quick summary of the spec changes in Syntax 0.8. Consult the
full [changelog][chlog0.8] for details.
This is only a quick summary of the spec changes in Syntax 0.8. Consult the
full [changelog][chlog0.8] for details.
[chlog0.8]: https://github.com/projectfluent/fluent/releases/tag/v0.8.0
[chlog0.8]: https://github.com/projectfluent/fluent/releases/tag/v0.8.0
In multiline `Patterns`, all common indent is now removed from each
indented line in the final value of the pattern.
In multiline `Patterns`, all common indent is now removed from each
indented line in the final value of the pattern.
```properties
multiline =
This message has 2 spaces of indent
on the second line of its value.
```
```properties
multiline =
This message has 2 spaces of indent
on the second line of its value.
```
`Terms` can now be parameterized via the call expression syntax. Only
variables defined between the parentheses in the message a term is used in
will be available inside of it.
`Terms` can now be parameterized via the call expression syntax. Only
variables defined between the parentheses in the message a term is used in
will be available inside of it.
```properties
# A parametrized Term with a Pattern as a value.
-thing = { $article ->
*[definite] the thing
[indefinite] a thing
}
```properties
# A parametrized Term with a Pattern as a value.
-thing = { $article ->
*[definite] the thing
[indefinite] a thing
}
this = This is { -thing(article: "indefinite") }.
```
this = This is { -thing(article: "indefinite") }.
```
`VariantLists` are now deprecated and will be removed from the Syntax
before version 1.0.
`VariantLists` are now deprecated and will be removed from the Syntax
before version 1.0.
All escapes sequences can only be used in `StringLiterals` now (see below).
`\UHHHHHH` is a new escape sequence format suitable for codepoints above
U+FFFF, e.g. `{"\U01F602"}`.
All escapes sequences can only be used in `StringLiterals` now (see below).
`\UHHHHHH` is a new escape sequence format suitable for codepoints above
U+FFFF, e.g. `{"\U01F602"}`.
### Backward-incompatible changes:
- The backslash character (`\`) is now considered a regular character in
`TextElements`. It's no longer possible to use escape sequences in
`TextElements`. Please use `StringLiterals` instead, e.g. `{"\u00A0"}`.
- The closing curly brace character (`}`) is not allowed in `TextElements`
now. Please use `StringLiterals` instead: `{"}"}`.
- The backslash character (`\`) is now considered a regular character in
`TextElements`. It's no longer possible to use escape sequences in
`TextElements`. Please use `StringLiterals` instead, e.g. `{"\u00A0"}`.
- The closing curly brace character (`}`) is not allowed in `TextElements`
now. Please use `StringLiterals` instead: `{"}"}`.
## fluent 0.9.1 (October 23, 2018)
- Forbid messages with `null` values and no attributes. (#299)
- Forbid messages with `null` values and no attributes. (#299)
Fix a parser behavior which caused it to parse messages without values
nor attributes as `"message-id": null`. This skewed the return values of
`FluentBundle.hasMessage` which would report `true` for messages which
were `null`. This, in turn, would break code which assumed
`FluentBundle.getMessage` would always return non-`null` values if it was
guarded by a call to `hasMessage` first.
Fix a parser behavior which caused it to parse messages without values
nor attributes as `"message-id": null`. This skewed the return values of
`FluentBundle.hasMessage` which would report `true` for messages which
were `null`. This, in turn, would break code which assumed
`FluentBundle.getMessage` would always return non-`null` values if it was
guarded by a call to `hasMessage` first.
## fluent 0.9.0 (October 23, 2018)

@@ -356,79 +357,78 @@

- Implement Fluent Syntax 0.7. (#287)
- Implement Fluent Syntax 0.7. (#287)
The major new feature of Syntax 0.7 is the relaxation of the indentation
requirement for all non-text elements of patterns. It's finally possible
to leave the closing brace of select expressions unindented:
The major new feature of Syntax 0.7 is the relaxation of the indentation
requirement for all non-text elements of patterns. It's finally possible
to leave the closing brace of select expressions unindented:
emails = { $unread_email_count ->
[one] You have one unread email.
*[other] You have { $unread_email_count } unread emails.
}
emails = { $unread_email_count ->
[one] You have one unread email.
*[other] You have { $unread_email_count } unread emails.
}
Consult the [changelog](https://github.com/projectfluent/fluent/releases/tag/v0.7.0)
to learn about other changes in Syntax 0.7.
Consult the [changelog](https://github.com/projectfluent/fluent/releases/tag/v0.7.0)
to learn about other changes in Syntax 0.7.
- Re-write the runtime parser. (#289)
- Re-write the runtime parser. (#289)
Syntax 0.7 was an opportunity to completely re-write the runtime parser,
which was originally created in the pre-0.1 era of Fluent. It's now less
than a half of the code size of the old parser and also slightly faster.
Syntax 0.7 was an opportunity to completely re-write the runtime parser,
which was originally created in the pre-0.1 era of Fluent. It's now less
than a half of the code size of the old parser and also slightly faster.
The parser takes an optimistic approach to parsing. It focuses on
minimizing the number of false negatives at the expense of increasing the
risk of false positives. In other words, it aims at parsing valid Fluent
messages with a success rate of 100%, but it may also parse a few invalid
messages which the reference parser would reject. The parser doesn't
perform strict validation of the all productions of the Fluent grammar.
It may thus produce entries which wouldn't make sense in the real world.
For best results users are advised to validate translations with the
`fluent-syntax` parser pre-runtime (e.g. by using Pontoon or
`compare-locales`).
The parser takes an optimistic approach to parsing. It focuses on
minimizing the number of false negatives at the expense of increasing the
risk of false positives. In other words, it aims at parsing valid Fluent
messages with a success rate of 100%, but it may also parse a few invalid
messages which the reference parser would reject. The parser doesn't
perform strict validation of the all productions of the Fluent grammar.
It may thus produce entries which wouldn't make sense in the real world.
For best results users are advised to validate translations with the
`fluent-syntax` parser pre-runtime (e.g. by using Pontoon or
`compare-locales`).
### Backward-incompatible changes
- Variant keys can now be either numbers (as previously) or identifiers.
Variant keys with spaces in them produce syntax errors, e.g. `[New York]`.
- `CR` is not a valid EOL character anymore. Please use `LF` or `CRLF`.
- `Tab` is not recognized as syntax whitespace. It can only be used in
translation content.
- Variant keys can now be either numbers (as previously) or identifiers.
Variant keys with spaces in them produce syntax errors, e.g. `[New York]`.
- `CR` is not a valid EOL character anymore. Please use `LF` or `CRLF`.
- `Tab` is not recognized as syntax whitespace. It can only be used in
translation content.
## fluent 0.8.1 (September 27, 2018)
- Expose `FluentResource` as an export. (#286)
- Expose `FluentResource` as an export. (#286)
`FluentResource` is a data structure representing a parsed Fluent document.
It can be used to cache resources which can then be added to `FluentBundle`
via the `addResource` method. To create a `FluentResource` given a string
of Fluent translations, use the static `FluentResource.fromString` method.
`FluentResource` is a data structure representing a parsed Fluent document.
It can be used to cache resources which can then be added to `FluentBundle`
via the `addResource` method. To create a `FluentResource` given a string
of Fluent translations, use the static `FluentResource.fromString` method.
```js
let resource = FluentResource.fromString(text);
bundle.addResource(resource);
```
```js
let resource = FluentResource.fromString(text);
bundle.addResource(resource);
```
The undocumented `_parse` export was also removed in favor of
`FluentResource.fromString`.
The undocumented `_parse` export was also removed in favor of
`FluentResource.fromString`.
## fluent 0.8.0 (August 20, 2018)
- Rename `MessageContext` to `FluentBundle`. (#222)
- Rename `MessageContext` to `FluentBundle`. (#222)
The following renames have been made to the public API:
The following renames have been made to the public API:
- Rename `MessageContext` to `FluentBundle`.
- Rename `MessageArgument` to `FluentType`.
- Rename `MessageNumberArgument` to `FluentNumber`.
- Rename `MessageDateTimeArgument` to `FluentDateTime`.
- Rename `MessageContext` to `FluentBundle`.
- Rename `MessageArgument` to `FluentType`.
- Rename `MessageNumberArgument` to `FluentNumber`.
- Rename `MessageDateTimeArgument` to `FluentDateTime`.
- Move `mapContext*` functions to [`fluent-sequence`][]. (#273)
- Move `mapContext*` functions to [`fluent-sequence`][]. (#273)
The `mapContextSync` and `mapContextAsync` functions previously exported
by the `fluent` package have been moved to the new [`fluent-sequence`][]
package. [`fluent-sequence`][] 0.1.0 corresponds to the exact
implementation of these functions from `fluent` 0.7.0.
The `mapContextSync` and `mapContextAsync` functions previously exported
by the `fluent` package have been moved to the new [`fluent-sequence`][]
package. [`fluent-sequence`][] 0.1.0 corresponds to the exact
implementation of these functions from `fluent` 0.7.0.
In later versions of [`fluent-sequence`][], these functions are called
`mapBundleSync` and `mapBundleAsync`.
In later versions of [`fluent-sequence`][], these functions are called
`mapBundleSync` and `mapBundleAsync`.

@@ -439,226 +439,223 @@ [`fluent-sequence`]: https://www.npmjs.com/package/fluent-sequence

- Implement support for Fluent Syntax 0.6.
- Implement support for Fluent Syntax 0.6.
Syntax 0.6 keeps the syntax unchanged and makes many small changes to the
previousl underspecified areas of the spec. The runtime parser now
supports Unicode escapes and properly trims whitespace in TextElements.
Syntax 0.6 keeps the syntax unchanged and makes many small changes to the
previousl underspecified areas of the spec. The runtime parser now
supports Unicode escapes and properly trims whitespace in TextElements.
- Add `FluentResource`. (#244)
- Add `FluentResource`. (#244)
`FluentResource` is a class representing a parsed Fluent document. It was
added with caching in mind. It's now possible to parse a Fluent document
inton an instance of `FluentResouce` once and use it to construct new
`MessageContexts`. For this end, `MessageContext` now has the
`addResource` method which takes an instance of `FluentResource`.
`FluentResource` is a class representing a parsed Fluent document. It was
added with caching in mind. It's now possible to parse a Fluent document
inton an instance of `FluentResouce` once and use it to construct new
`MessageContexts`. For this end, `MessageContext` now has the
`addResource` method which takes an instance of `FluentResource`.
- Add the `transform` option to `MessageContext`. (#213)
- Add the `transform` option to `MessageContext`. (#213)
`MessageContext` now accepts a new option, `transform`, which may be a
function. If passed it will be used to transform the string parts of
patterns. This may be used to implement programmatic transformations of
translations, e.g. to create pseudo-localizations.
`MessageContext` now accepts a new option, `transform`, which may be a
function. If passed it will be used to transform the string parts of
patterns. This may be used to implement programmatic transformations of
translations, e.g. to create pseudo-localizations.
- Drop support for IE and old evergreen browsers. (#133)
- Drop support for IE and old evergreen browsers. (#133)
Currently supported are: Firefox 52+, Chrome 55+, Edge 15+, Safari 10.1+,
iOS Safari 10.3+ and node 8.9+.
Currently supported are: Firefox 52+, Chrome 55+, Edge 15+, Safari 10.1+,
iOS Safari 10.3+ and node 8.9+.
- Move `CachedSyncIterable` and `CachedAsyncIterable` to a dependency.
- Move `CachedSyncIterable` and `CachedAsyncIterable` to a dependency.
They are now available from the `cached-iterable` package. `fluent`
depends on it for running tests.
They are now available from the `cached-iterable` package. `fluent`
depends on it for running tests.
## fluent 0.6.4 (April 11, 2018)
- Minor optimization to bidirectionality isolation
- Added error logging when attempting an already registered message id
- Minor optimization to bidirectionality isolation
- Added error logging when attempting an already registered message id
## fluent 0.6.3 (February 9, 2018)
- Update sinon to 4.2.2
- Update sinon to 4.2.2
## fluent 0.6.2 (February 8, 2018)
- Correctly parse empty comment lines. (#149)
- Forbid null attribute and variant values. (part of #150)
- Correctly parse empty comment lines. (#149)
- Forbid null attribute and variant values. (part of #150)
## fluent 0.6.0 (January 31, 2018)
- Implement Fluent Syntax 0.5.
- Implement Fluent Syntax 0.5.
- Add support for terms.
- Add support for `#`, `##` and `###` comments.
- Remove support for tags.
- Add support for `=` after the identifier in message and term
defintions.
- Forbid newlines in string expressions.
- Allow trailing comma in call expression argument lists.
- Add support for terms.
- Add support for `#`, `##` and `###` comments.
- Remove support for tags.
- Add support for `=` after the identifier in message and term
defintions.
- Forbid newlines in string expressions.
- Allow trailing comma in call expression argument lists.
In fluent 0.6.x the new Syntax 0.5 is supported alongside the old Syntax
0.4. This should make migrations easier. The parser will correctly parse
Syntax 0.4 comments (prefixed with `//`), sections and message
definitions without the `=` after the identifier. The one exception are
tags which are no longer supported. Please use attributed defined on
terms instead.
In fluent 0.6.x the new Syntax 0.5 is supported alongside the old Syntax
0.4. This should make migrations easier. The parser will correctly parse
Syntax 0.4 comments (prefixed with `//`), sections and message
definitions without the `=` after the identifier. The one exception are
tags which are no longer supported. Please use attributed defined on
terms instead.
- Add `mapContextAsync`. (#125)
- Add `mapContextAsync`. (#125)
This is the async counterpart to mapContextSync. Given an async iterable
of `MessageContext` instances and an array of ids (or a single id), it
maps each identifier to the first `MessageContext` which contains the
message for it.
This is the async counterpart to mapContextSync. Given an async iterable
of `MessageContext` instances and an array of ids (or a single id), it
maps each identifier to the first `MessageContext` which contains the
message for it.
An ordered interable of `MessageContext` instances can represent the
current negotiated fallback chain of languages. This iterable can be used
to find the best existing translation for a given identifier.
An ordered interable of `MessageContext` instances can represent the
current negotiated fallback chain of languages. This iterable can be used
to find the best existing translation for a given identifier.
The iterable of `MessageContexts` can now be async, allowing code like
this:
The iterable of `MessageContexts` can now be async, allowing code like
this:
```js
async formatString(id, args) {
const bundle = await mapContextAsync(bundles, id);
```js
async formatString(id, args) {
const bundle = await mapContextAsync(bundles, id);
if (bundle === null) {
return id;
}
if (bundle === null) {
return id;
}
const msg = bundle.getMessage(id);
return bundle.format(msg, args);
}
```
const msg = bundle.getMessage(id);
return bundle.format(msg, args);
}
```
The iterable of `MessageContexts` should always be wrapped in
`CachedIterable` to optimize subsequent calls to `mapContextSync` and
`mapContextAsync`.
The iterable of `MessageContexts` should always be wrapped in
`CachedIterable` to optimize subsequent calls to `mapContextSync` and
`mapContextAsync`.
Because `mapContextAsync` uses asynchronous iteration you'll likely need
the regenerator runtime provided by `babel-polyfill` to run the `compat`
builds of `fluent`.
Because `mapContextAsync` uses asynchronous iteration you'll likely need
the regenerator runtime provided by `babel-polyfill` to run the `compat`
builds of `fluent`.
- Expose the `ftl` dedent helper.
- Expose the `ftl` dedent helper.
The `ftl` template literal tag can be used to conveniently include FTL
snippets in other code. It strips the common indentation from the snippet
allowing it to be indented on the level dictated by the current code
indentation.
The `ftl` template literal tag can be used to conveniently include FTL
snippets in other code. It strips the common indentation from the snippet
allowing it to be indented on the level dictated by the current code
indentation.
```js
bundle.addMessages(ftl`
foo = Foo
bar = Bar
);
```
```js
bundle.addMessages(ftl`
foo = Foo
bar = Bar
);
```
- Remove `MessageContext.formatToParts`.
- Remove `MessageContext.formatToParts`.
It's only use-case was passing React elements as arguments to
translations which is now possible thanks to DOM overlays (#101).
It's only use-case was passing React elements as arguments to
translations which is now possible thanks to DOM overlays (#101).
- Rename `FluentType.valueOf` to `FluentType.toString1.
- Rename `FluentType.valueOf` to `FluentType.toString1.
Without `MessageContext.formatToParts`, all use-cases for
`FluentType.valueOf` boil down to stringification.
Without `MessageContext.formatToParts`, all use-cases for
`FluentType.valueOf` boil down to stringification.
- Remove `FluentType.isTypeOf`.
- Remove `FluentType.isTypeOf`.
fluent-react's markup overlays (#101) removed the dependency on fluent's
`FluentType` which was hardcoded as an import from fluent/compat. Without
this dependency all imports from fluent are in the hands of developers
again and they can decide to use the ES2015+ or the compat builds as they
wish. As long as they do it consistently, regular instanceof checks will
work well.
fluent-react's markup overlays (#101) removed the dependency on fluent's
`FluentType` which was hardcoded as an import from fluent/compat. Without
this dependency all imports from fluent are in the hands of developers
again and they can decide to use the ES2015+ or the compat builds as they
wish. As long as they do it consistently, regular instanceof checks will
work well.
## fluent 0.4.2 (November 27, 2017)
- Add touchNext to CachedIterable.
- Add touchNext to CachedIterable.
This allows the user of CachedIterable to trigger construction of an
element yielded from the generator early.
This allows the user of CachedIterable to trigger construction of an
element yielded from the generator early.
- Add the static FluentType.isTypeOf method.
- Add the static FluentType.isTypeOf method.
In some cases, bundlers such as Webpack would break the instanceof
FluentType check. The new FluentType.isTypeOf static method can be used
instead to guarantee the proper behavior.
In some cases, bundlers such as Webpack would break the instanceof
FluentType check. The new FluentType.isTypeOf static method can be used
instead to guarantee the proper behavior.
- Catch errors thrown by Intl formatters.
- Catch errors thrown by Intl formatters.
## fluent 0.4.1 (June 22, 2017)
- Introduce mapContextSync and CachedIterable.
- Introduce mapContextSync and CachedIterable.
An ordered iterable of MessageContext instances can represent the
current negotiated fallback chain of languages. This iterable can be
used to find the best existing translation for a given identifier.
An ordered iterable of MessageContext instances can represent the
current negotiated fallback chain of languages. This iterable can be
used to find the best existing translation for a given identifier.
The mapContext* methods can be used to find the first MessageContext in
the given iterable which contains the translation with the given
identifier. If the iterable is ordered according to the result of
a language negotiation the returned MessageContext contains the best
available translation.
The mapContext\* methods can be used to find the first MessageContext in
the given iterable which contains the translation with the given
identifier. If the iterable is ordered according to the result of
a language negotiation the returned MessageContext contains the best
available translation.
A simple function which formats translations based on the identifier
might be implemented as follows:
A simple function which formats translations based on the identifier
might be implemented as follows:
getString(id, args) {
const bundle = mapContextSync(bundles, id);
getString(id, args) {
const bundle = mapContextSync(bundles, id);
if (bundle === null) {
return id;
}
if (bundle === null) {
return id;
}
const msg = bundle.getMessage(id);
return bundle.format(msg, args);
}
const msg = bundle.getMessage(id);
return bundle.format(msg, args);
}
In order to pass an iterator to mapContext*, wrap it in CachedIterable.
This allows multiple calls to mapContext* without advancing and
eventually depleting the iterator.
In order to pass an iterator to mapContext*, wrap it in CachedIterable.
This allows multiple calls to mapContext* without advancing and
eventually depleting the iterator.
function *generateMessages() {
// Some lazy logic for yielding MessageContexts.
yield *[bundle1, bundle2];
}
function *generateMessages() {
// Some lazy logic for yielding MessageContexts.
yield *[bundle1, bundle2];
}
const bundles = new CachedIterable(generateMessages());
const bundle = mapContextSync(bundles, id);
const bundles = new CachedIterable(generateMessages());
const bundle = mapContextSync(bundles, id);
## fluent 0.4.0 (May 17th, 2017)
- Added MessageContext.hasMessage and MessageContext.getMessage methods.
- Added MessageContext.hasMessage and MessageContext.getMessage methods.
Using the MessageContext.messages map for getting raw messages is
deprecated now. Instead, use the two dedicated methods: hasMessage and
getMessage.
Using the MessageContext.messages map for getting raw messages is
deprecated now. Instead, use the two dedicated methods: hasMessage and
getMessage.
Before:
Before:
const msg = bundle.messages.get(id);
const txt = bundle.format(msg);
const msg = bundle.messages.get(id);
const txt = bundle.format(msg);
Now:
Now:
const msg = bundle.getMessage(id);
const txt = bundle.format(msg);
const msg = bundle.getMessage(id);
const txt = bundle.format(msg);
- The compat build is now transpiled using rollup-plugin-babel.
- The compat build is now transpiled using rollup-plugin-babel.
This ensures that the "use strict" pragma is scoped to the UMD wrapper. It
also correctly parses the top-level "this" keyword (which the previous
setup turned into "undefined").
This ensures that the "use strict" pragma is scoped to the UMD wrapper. It
also correctly parses the top-level "this" keyword (which the previous
setup turned into "undefined").
## fluent 0.3.1
- MessageContext takes one locale (a string) or an array of locales.
- Add the "module" field to package.json for ES modules-compatible bundlers.
- MessageContext takes one locale (a string) or an array of locales.
- Add the "module" field to package.json for ES modules-compatible bundlers.
## fluent 0.3.0
- Support Fluent Syntax 0.3
- Support Fluent Syntax 0.3
Added support for tags and indented multiline text. Removed quoted values.
Added support for tags and indented multiline text. Removed quoted values.

@@ -669,47 +666,47 @@ ## fluent 0.2.2

- (ef99a62) Remove the custom `Intl.PluralRules` polyfill
- (ef99a62) Remove the custom `Intl.PluralRules` polyfill
You'll need to provide your own polyfill or use `fluent-intl-polyfill`.
You'll need to provide your own polyfill or use `fluent-intl-polyfill`.
- (1a4f2a8) Make fluent and fluent-syntax separate packages
- (1a4f2a8) Make fluent and fluent-syntax separate packages
Install the `fluent-syntax` package to use the AST parser.
Install the `fluent-syntax` package to use the AST parser.
### New features
- (73fbab8) Add `MessageContext.formatToParts`
- (2f51e27) Export `FluentNumber` as `MessageArgument`
- (de01c8b) Provide a compat version using babel-preset-latest
- (73fbab8) Add `MessageContext.formatToParts`
- (2f51e27) Export `FluentNumber` as `MessageArgument`
- (de01c8b) Provide a compat version using babel-preset-latest
Use it by importing from `fluent/compat'
Use it by importing from `fluent/compat'
### Other
- (44fa872) Remove a special case for `FluentNumber` in `SelectExpression`
- (7dfa787) Remove namespaces from keywords
- (2fff487) Use `FluentNumber`'s value when matching `FluentKeywords`
- (b4bcbdd) Remove tests which were causing parsing errors
- (0f35313) Name the AMD module 'fluent'
- (44fa872) Remove a special case for `FluentNumber` in `SelectExpression`
- (7dfa787) Remove namespaces from keywords
- (2fff487) Use `FluentNumber`'s value when matching `FluentKeywords`
- (b4bcbdd) Remove tests which were causing parsing errors
- (0f35313) Name the AMD module 'fluent'
## fluent 0.2.1
- (d247e5d) npm install fluent
- (d00fe3a) Remove `JunkEntries` from the RuntimeParser
- (d247e5d) npm install fluent
- (d00fe3a) Remove `JunkEntries` from the RuntimeParser
## fluent 0.2.0
- (1f86c8f) Update the AST parser to Syntax 0.2
- (679ca3e) Update the Runtime parser to Syntax 0.2
- (d3681d7) Update the resolver to Syntax 0.2.
- (bfea90e) Rename `Entity` to `Message`
- (5efd0f9) Remove `FTLList`
- (282cccb) Add `JunkEntry` to AST parser
- (a78a32a) Add `lineNumber` and `columnNumber` to SyntaxErrors
- (e0245f4) Always print JSON in tools/parse
- (5d788b7) Move the AST parser to src/syntax
- (1f86c8f) Update the AST parser to Syntax 0.2
- (679ca3e) Update the Runtime parser to Syntax 0.2
- (d3681d7) Update the resolver to Syntax 0.2.
- (bfea90e) Rename `Entity` to `Message`
- (5efd0f9) Remove `FTLList`
- (282cccb) Add `JunkEntry` to AST parser
- (a78a32a) Add `lineNumber` and `columnNumber` to SyntaxErrors
- (e0245f4) Always print JSON in tools/parse
- (5d788b7) Move the AST parser to src/syntax
## fluent 0.1.0
- (0d38714) Fluent.js 0.1
- (0d38714) Fluent.js 0.1
The initial release based on l20n.js c3e35c4.
The initial release based on l20n.js c3e35c4.

@@ -1,2 +0,7 @@

export declare type Message = {
/**
* Raw messages are `{value, attributes}` shapes containing translation units
* called `Patterns`. `Patterns` are implementation-specific; they should be
* treated as black boxes and formatted with `FluentBundle.formatPattern`.
*/
export type Message = {
id: string;

@@ -6,3 +11,3 @@ value: Pattern | null;

};
export declare type Term = {
export type Term = {
id: string;

@@ -12,7 +17,7 @@ value: Pattern;

};
export declare type Pattern = string | ComplexPattern;
export declare type ComplexPattern = Array<PatternElement>;
export declare type PatternElement = string | Expression;
export declare type Expression = SelectExpression | VariableReference | TermReference | MessageReference | FunctionReference | Literal;
export declare type SelectExpression = {
export type Pattern = string | ComplexPattern;
export type ComplexPattern = Array<PatternElement>;
export type PatternElement = string | Expression;
export type Expression = SelectExpression | VariableReference | TermReference | MessageReference | FunctionReference | Literal;
export type SelectExpression = {
type: "select";

@@ -23,7 +28,7 @@ selector: Expression;

};
export declare type VariableReference = {
export type VariableReference = {
type: "var";
name: string;
};
export declare type TermReference = {
export type TermReference = {
type: "term";

@@ -34,3 +39,3 @@ name: string;

};
export declare type MessageReference = {
export type MessageReference = {
type: "mesg";

@@ -40,3 +45,3 @@ name: string;

};
export declare type FunctionReference = {
export type FunctionReference = {
type: "func";

@@ -46,7 +51,7 @@ name: string;

};
export declare type Variant = {
export type Variant = {
key: Literal;
value: Pattern;
};
export declare type NamedArgument = {
export type NamedArgument = {
type: "narg";

@@ -56,8 +61,8 @@ name: string;

};
export declare type Literal = StringLiteral | NumberLiteral;
export declare type StringLiteral = {
export type Literal = StringLiteral | NumberLiteral;
export type StringLiteral = {
type: "str";
value: string;
};
export declare type NumberLiteral = {
export type NumberLiteral = {
type: "num";

@@ -64,0 +69,0 @@ value: number;

@@ -13,3 +13,3 @@ /**

*/
import { FluentNone, FluentNumber, FluentDateTime } from "./types.js";
import { FluentNone, FluentNumber, FluentDateTime, } from "./types.js";
function values(opts, allowed) {

@@ -70,3 +70,3 @@ const unwrapped = Object.create(null);

...arg.opts,
...values(opts, NUMBER_ALLOWED)
...values(opts, NUMBER_ALLOWED),
});

@@ -76,3 +76,3 @@ }

return new FluentNumber(arg.valueOf(), {
...values(opts, NUMBER_ALLOWED)
...values(opts, NUMBER_ALLOWED),
});

@@ -140,3 +140,3 @@ }

...arg.opts,
...values(opts, DATETIME_ALLOWED)
...values(opts, DATETIME_ALLOWED),
});

@@ -146,3 +146,3 @@ }

return new FluentDateTime(arg.valueOf(), {
...values(opts, DATETIME_ALLOWED)
...values(opts, DATETIME_ALLOWED),
});

@@ -149,0 +149,0 @@ }

@@ -5,5 +5,4 @@ import { FluentResource } from "./resource.js";

import { IntlCache } from "./memoizer.js";
export declare type TextTransform = (text: string) => string;
declare type NativeValue = string | number | Date;
export declare type FluentVariable = FluentValue | NativeValue;
export type TextTransform = (text: string) => string;
export type FluentVariable = FluentValue | string | number | Date;
/**

@@ -15,7 +14,13 @@ * Message bundles are single-language stores of translation resources. They are

locales: Array<string>;
/** @ignore */
_terms: Map<string, Term>;
/** @ignore */
_messages: Map<string, Message>;
/** @ignore */
_functions: Record<string, FluentFunction>;
/** @ignore */
_useIsolating: boolean;
/** @ignore */
_transform: TextTransform;
/** @ignore */
_intls: IntlCache;

@@ -25,31 +30,29 @@ /**

*
* The `locales` argument is used to instantiate `Intl` formatters used by
* translations. The `options` object can be used to configure the bundle.
* @example
* ```js
* let bundle = new FluentBundle(["en-US", "en"]);
*
* Examples:
* let bundle = new FluentBundle(locales, {useIsolating: false});
*
* let bundle = new FluentBundle(["en-US", "en"]);
* let bundle = new FluentBundle(locales, {
* useIsolating: true,
* functions: {
* NODE_ENV: () => process.env.NODE_ENV
* }
* });
* ```
*
* let bundle = new FluentBundle(locales, {useIsolating: false});
*
* let bundle = new FluentBundle(locales, {
* useIsolating: true,
* functions: {
* NODE_ENV: () => process.env.NODE_ENV
* }
* });
*
* Available options:
*
* - `functions` - an object of additional functions available to
* translations as builtins.
*
* - `useIsolating` - boolean specifying whether to use Unicode isolation
* marks (FSI, PDI) for bidi interpolations. Default: `true`.
*
* - `transform` - a function used to transform string parts of patterns.
* @param locales - Used to instantiate `Intl` formatters used by translations.
* @param options - Optional configuration for the bundle.
*/
constructor(locales: string | Array<string>, { functions, useIsolating, transform }?: {
constructor(locales: string | Array<string>, { functions, useIsolating, transform, }?: {
/** Additional functions available to translations as builtins. */
functions?: Record<string, FluentFunction>;
/**
* Whether to use Unicode isolation marks (FSI, PDI) for bidi interpolations.
*
* Default: `true`.
*/
useIsolating?: boolean;
/** A function used to transform string parts of patterns. */
transform?: TextTransform;

@@ -76,18 +79,20 @@ });

*
* The translation resource must be an instance of `FluentResource`.
* @example
* ```js
* let res = new FluentResource("foo = Foo");
* bundle.addResource(res);
* bundle.getMessage("foo");
* // → {value: .., attributes: {..}}
* ```
*
* let res = new FluentResource("foo = Foo");
* bundle.addResource(res);
* bundle.getMessage("foo");
* // → {value: .., attributes: {..}}
*
* Available options:
*
* - `allowOverrides` - boolean specifying whether it's allowed to override
* an existing message or term with a new value. Default: `false`.
*
* @param res - FluentResource object.
* @param options
* @param res
* @param options
*/
addResource(res: FluentResource, { allowOverrides }?: {
addResource(res: FluentResource, { allowOverrides, }?: {
/**
* Boolean specifying whether it's allowed to override
* an existing message or term with a new value.
*
* Default: `false`.
*/
allowOverrides?: boolean;

@@ -106,20 +111,22 @@ }): Array<Error>;

*
* let errors = [];
* bundle.addResource(
* new FluentResource("hello = Hello, {$name}!"));
* If `errors` is omitted, the first encountered error will be thrown.
*
* let hello = bundle.getMessage("hello");
* if (hello.value) {
* bundle.formatPattern(hello.value, {name: "Jane"}, errors);
* // Returns "Hello, Jane!" and `errors` is empty.
* @example
* ```js
* let errors = [];
* bundle.addResource(
* new FluentResource("hello = Hello, {$name}!"));
*
* bundle.formatPattern(hello.value, undefined, errors);
* // Returns "Hello, {$name}!" and `errors` is now:
* // [<ReferenceError: Unknown variable: name>]
* }
* let hello = bundle.getMessage("hello");
* if (hello.value) {
* bundle.formatPattern(hello.value, {name: "Jane"}, errors);
* // Returns "Hello, Jane!" and `errors` is empty.
*
* If `errors` is omitted, the first encountered error will be thrown.
* bundle.formatPattern(hello.value, undefined, errors);
* // Returns "Hello, {$name}!" and `errors` is now:
* // [<ReferenceError: Unknown variable: name>]
* }
* ```
*/
formatPattern(pattern: Pattern, args?: Record<string, FluentVariable> | null, errors?: Array<Error> | null): string;
}
export {};

@@ -14,30 +14,23 @@ import { resolveComplexPattern } from "./resolver.js";

*
* The `locales` argument is used to instantiate `Intl` formatters used by
* translations. The `options` object can be used to configure the bundle.
* @example
* ```js
* let bundle = new FluentBundle(["en-US", "en"]);
*
* Examples:
* let bundle = new FluentBundle(locales, {useIsolating: false});
*
* let bundle = new FluentBundle(["en-US", "en"]);
* let bundle = new FluentBundle(locales, {
* useIsolating: true,
* functions: {
* NODE_ENV: () => process.env.NODE_ENV
* }
* });
* ```
*
* let bundle = new FluentBundle(locales, {useIsolating: false});
*
* let bundle = new FluentBundle(locales, {
* useIsolating: true,
* functions: {
* NODE_ENV: () => process.env.NODE_ENV
* }
* });
*
* Available options:
*
* - `functions` - an object of additional functions available to
* translations as builtins.
*
* - `useIsolating` - boolean specifying whether to use Unicode isolation
* marks (FSI, PDI) for bidi interpolations. Default: `true`.
*
* - `transform` - a function used to transform string parts of patterns.
* @param locales - Used to instantiate `Intl` formatters used by translations.
* @param options - Optional configuration for the bundle.
*/
constructor(locales, { functions, useIsolating = true, transform = (v) => v } = {}) {
constructor(locales, { functions, useIsolating = true, transform = (v) => v, } = {}) {
/** @ignore */
this._terms = new Map();
/** @ignore */
this._messages = new Map();

@@ -48,3 +41,3 @@ this.locales = Array.isArray(locales) ? locales : [locales];

DATETIME,
...functions
...functions,
};

@@ -78,18 +71,14 @@ this._useIsolating = useIsolating;

*
* The translation resource must be an instance of `FluentResource`.
* @example
* ```js
* let res = new FluentResource("foo = Foo");
* bundle.addResource(res);
* bundle.getMessage("foo");
* // → {value: .., attributes: {..}}
* ```
*
* let res = new FluentResource("foo = Foo");
* bundle.addResource(res);
* bundle.getMessage("foo");
* // → {value: .., attributes: {..}}
*
* Available options:
*
* - `allowOverrides` - boolean specifying whether it's allowed to override
* an existing message or term with a new value. Default: `false`.
*
* @param res - FluentResource object.
* @param options
* @param res
* @param options
*/
addResource(res, { allowOverrides = false } = {}) {
addResource(res, { allowOverrides = false, } = {}) {
const errors = [];

@@ -128,17 +117,20 @@ for (let i = 0; i < res.body.length; i++) {

*
* let errors = [];
* bundle.addResource(
* new FluentResource("hello = Hello, {$name}!"));
* If `errors` is omitted, the first encountered error will be thrown.
*
* let hello = bundle.getMessage("hello");
* if (hello.value) {
* bundle.formatPattern(hello.value, {name: "Jane"}, errors);
* // Returns "Hello, Jane!" and `errors` is empty.
* @example
* ```js
* let errors = [];
* bundle.addResource(
* new FluentResource("hello = Hello, {$name}!"));
*
* bundle.formatPattern(hello.value, undefined, errors);
* // Returns "Hello, {$name}!" and `errors` is now:
* // [<ReferenceError: Unknown variable: name>]
* }
* let hello = bundle.getMessage("hello");
* if (hello.value) {
* bundle.formatPattern(hello.value, {name: "Jane"}, errors);
* // Returns "Hello, Jane!" and `errors` is empty.
*
* If `errors` is omitted, the first encountered error will be thrown.
* bundle.formatPattern(hello.value, undefined, errors);
* // Returns "Hello, {$name}!" and `errors` is now:
* // [<ReferenceError: Unknown variable: name>]
* }
* ```
*/

@@ -145,0 +137,0 @@ formatPattern(pattern, args = null, errors = null) {

@@ -9,4 +9,6 @@ /**

*/
export type { Message } from "./ast.js";
export { FluentBundle, FluentVariable, TextTransform } from "./bundle.js";
export { FluentResource } from "./resource.js";
export { FluentValue, FluentType, FluentFunction, FluentNone, FluentNumber, FluentDateTime } from "./types.js";
export type { Scope } from "./scope.js";
export { FluentValue, FluentType, FluentFunction, FluentNone, FluentNumber, FluentDateTime, } from "./types.js";

@@ -11,2 +11,2 @@ /**

export { FluentResource } from "./resource.js";
export { FluentType, FluentNone, FluentNumber, FluentDateTime } from "./types.js";
export { FluentType, FluentNone, FluentNumber, FluentDateTime, } from "./types.js";

@@ -1,2 +0,2 @@

export declare type IntlCache = Map<typeof Intl.NumberFormat | typeof Intl.DateTimeFormat | typeof Intl.PluralRules, Record<string, Intl.NumberFormat | Intl.DateTimeFormat | Intl.PluralRules>>;
export type IntlCache = Map<typeof Intl.NumberFormat | typeof Intl.DateTimeFormat | typeof Intl.PluralRules, Record<string, Intl.NumberFormat | Intl.DateTimeFormat | Intl.PluralRules>>;
export declare function getMemoizerForLocale(locales: string | string[]): IntlCache;

@@ -27,2 +27,3 @@ /**

import { ComplexPattern } from "./ast.js";
/** Resolve a pattern (a complex string with placeables). */
export declare function resolveComplexPattern(scope: Scope, ptn: ComplexPattern): FluentValue;

@@ -25,11 +25,13 @@ /* global Intl */

*/
import { FluentType, FluentNone, FluentNumber, FluentDateTime } from "./types.js";
// The maximum number of placeables which can be expanded in a single call to
// `formatPattern`. The limit protects against the Billion Laughs and Quadratic
// Blowup attacks. See https://msdn.microsoft.com/en-us/magazine/ee335713.aspx.
import { FluentType, FluentNone, FluentNumber, FluentDateTime, } from "./types.js";
/**
* The maximum number of placeables which can be expanded in a single call to
* `formatPattern`. The limit protects against the Billion Laughs and Quadratic
* Blowup attacks. See https://msdn.microsoft.com/en-us/magazine/ee335713.aspx.
*/
const MAX_PLACEABLES = 100;
// Unicode bidi isolation characters.
/** Unicode bidi isolation characters. */
const FSI = "\u2068";
const PDI = "\u2069";
// Helper: match a variant key to the given selector.
/** Helper: match a variant key to the given selector. */
function match(scope, selector, key) {

@@ -56,3 +58,3 @@ if (key === selector) {

}
// Helper: resolve the default variant from a list of variants.
/** Helper: resolve the default variant from a list of variants. */
function getDefault(scope, variants, star) {

@@ -65,3 +67,3 @@ if (variants[star]) {

}
// Helper: resolve arguments to a call expression.
/** Helper: resolve arguments to a call expression. */
function getArguments(scope, args) {

@@ -80,3 +82,3 @@ const positional = [];

}
// Resolve an expression to a Fluent type.
/** Resolve an expression to a Fluent type. */
function resolveExpression(scope, expr) {

@@ -88,3 +90,3 @@ switch (expr.type) {

return new FluentNumber(expr.value, {
minimumFractionDigits: expr.precision
minimumFractionDigits: expr.precision,
});

@@ -105,3 +107,3 @@ case "var":

}
// Resolve a reference to a variable.
/** Resolve a reference to a variable. */
function resolveVariableReference(scope, { name }) {

@@ -118,4 +120,4 @@ let arg;

}
else if (scope.args
&& Object.prototype.hasOwnProperty.call(scope.args, name)) {
else if (scope.args &&
Object.prototype.hasOwnProperty.call(scope.args, name)) {
// We're in the top-level Pattern or inside a MessageReference. Missing

@@ -149,3 +151,3 @@ // variables references produce ReferenceErrors.

}
// Resolve a reference to another message.
/** Resolve a reference to another message. */
function resolveMessageReference(scope, { name, attr }) {

@@ -171,3 +173,3 @@ const message = scope.bundle._messages.get(name);

}
// Resolve a call to a Term with key-value arguments.
/** Resolve a call to a Term with key-value arguments. */
function resolveTermReference(scope, { name, attr, args }) {

@@ -197,3 +199,3 @@ const id = `-${name}`;

}
// Resolve a call to a Function with positional and key-value arguments.
/** Resolve a call to a Function with positional and key-value arguments. */
function resolveFunctionReference(scope, { name, args }) {

@@ -220,3 +222,3 @@ // Some functions are built-in. Others may be provided by the runtime via

}
// Resolve a select expression to the member object.
/** Resolve a select expression to the member object. */
function resolveSelectExpression(scope, { selector, variants, star }) {

@@ -236,3 +238,3 @@ let sel = resolveExpression(scope, selector);

}
// Resolve a pattern (a complex string with placeables).
/** Resolve a pattern (a complex string with placeables). */
export function resolveComplexPattern(scope, ptn) {

@@ -275,4 +277,6 @@ if (scope.dirty.has(ptn)) {

}
// Resolve a simple or a complex Pattern to a FluentString (which is really the
// string primitive).
/**
* Resolve a simple or a complex Pattern to a FluentString
* (which is really the string primitive).
*/
function resolvePattern(scope, value) {

@@ -279,0 +283,0 @@ // Resolve a simple pattern.

@@ -6,4 +6,5 @@ import { Message, Term } from "./ast.js";

export declare class FluentResource {
/** @ignore */
body: Array<Message | Term>;
constructor(source: string);
}

@@ -229,3 +229,3 @@ // This regex is used to iterate through the beginnings of messages and terms.

selector,
...variants
...variants,
};

@@ -262,3 +262,3 @@ }

attr,
args: []
args: [],
};

@@ -295,3 +295,3 @@ }

name: expr.name,
value: parseLiteral()
value: parseLiteral(),
};

@@ -334,3 +334,3 @@ }

type: "str",
value: match1(RE_IDENTIFIER)
value: match1(RE_IDENTIFIER),
};

@@ -356,3 +356,3 @@ }

value: parseFloat(value),
precision
precision,
};

@@ -385,7 +385,7 @@ }

return codepoint <= 0xd7ff || 0xe000 <= codepoint
// It's a Unicode scalar value.
? String.fromCodePoint(codepoint)
// Lonely surrogates can cause trouble when the parsing result is
// saved using UTF-8. Use U+FFFD REPLACEMENT CHARACTER instead.
: "�";
? // It's a Unicode scalar value.
String.fromCodePoint(codepoint)
: // Lonely surrogates can cause trouble when the parsing result is
// saved using UTF-8. Use U+FFFD REPLACEMENT CHARACTER instead.
"�";
}

@@ -392,0 +392,0 @@ throw new SyntaxError("Unknown escape sequence");

@@ -10,9 +10,15 @@ import { FluentBundle, FluentVariable } from "./bundle.js";

args: Record<string, FluentVariable> | null;
/** The Set of patterns already encountered during this resolution.
* Used to detect and prevent cyclic resolutions. */
/**
* The Set of patterns already encountered during this resolution.
* Used to detect and prevent cyclic resolutions.
* @ignore
*/
dirty: WeakSet<ComplexPattern>;
/** A dict of parameters passed to a TermReference. */
params: Record<string, FluentVariable> | null;
/** The running count of placeables resolved so far. Used to detect the
* Billion Laughs and Quadratic Blowup attacks. */
/**
* The running count of placeables resolved so far.
* Used to detect the Billion Laughs and Quadratic Blowup attacks.
* @ignore
*/
placeables: number;

@@ -19,0 +25,0 @@ constructor(bundle: FluentBundle, errors: Array<Error> | null, args: Record<string, FluentVariable> | null);

export class Scope {
constructor(bundle, errors, args) {
/** The Set of patterns already encountered during this resolution.
* Used to detect and prevent cyclic resolutions. */
/**
* The Set of patterns already encountered during this resolution.
* Used to detect and prevent cyclic resolutions.
* @ignore
*/
this.dirty = new WeakSet();
/** A dict of parameters passed to a TermReference. */
this.params = null;
/** The running count of placeables resolved so far. Used to detect the
* Billion Laughs and Quadratic Blowup attacks. */
/**
* The running count of placeables resolved so far.
* Used to detect the Billion Laughs and Quadratic Blowup attacks.
* @ignore
*/
this.placeables = 0;

@@ -11,0 +17,0 @@ this.bundle = bundle;

import { Scope } from "./scope.js";
export declare type FluentValue = FluentType<unknown> | string;
export declare type FluentFunction = (positional: Array<FluentValue>, named: Record<string, FluentValue>) => FluentValue;
export type FluentValue = FluentType<unknown> | string;
export type FluentFunction = (positional: Array<FluentValue>, named: Record<string, FluentValue>) => FluentValue;
/**

@@ -5,0 +5,0 @@ * The `FluentType` class is the base of Fluent's type system.

@@ -1,2 +0,2 @@

/* @fluent/bundle@0.17.1 */
/** @fluent/bundle@0.18.0 */
(function (global, factory) {

@@ -118,10 +118,12 @@ typeof exports === 'object' && typeof module !== 'undefined' ? factory(exports) :

/* global Intl */
// The maximum number of placeables which can be expanded in a single call to
// `formatPattern`. The limit protects against the Billion Laughs and Quadratic
// Blowup attacks. See https://msdn.microsoft.com/en-us/magazine/ee335713.aspx.
/**
* The maximum number of placeables which can be expanded in a single call to
* `formatPattern`. The limit protects against the Billion Laughs and Quadratic
* Blowup attacks. See https://msdn.microsoft.com/en-us/magazine/ee335713.aspx.
*/
const MAX_PLACEABLES = 100;
// Unicode bidi isolation characters.
/** Unicode bidi isolation characters. */
const FSI = "\u2068";
const PDI = "\u2069";
// Helper: match a variant key to the given selector.
/** Helper: match a variant key to the given selector. */
function match(scope, selector, key) {

@@ -148,3 +150,3 @@ if (key === selector) {

}
// Helper: resolve the default variant from a list of variants.
/** Helper: resolve the default variant from a list of variants. */
function getDefault(scope, variants, star) {

@@ -157,3 +159,3 @@ if (variants[star]) {

}
// Helper: resolve arguments to a call expression.
/** Helper: resolve arguments to a call expression. */
function getArguments(scope, args) {

@@ -172,3 +174,3 @@ const positional = [];

}
// Resolve an expression to a Fluent type.
/** Resolve an expression to a Fluent type. */
function resolveExpression(scope, expr) {

@@ -180,3 +182,3 @@ switch (expr.type) {

return new FluentNumber(expr.value, {
minimumFractionDigits: expr.precision
minimumFractionDigits: expr.precision,
});

@@ -197,3 +199,3 @@ case "var":

}
// Resolve a reference to a variable.
/** Resolve a reference to a variable. */
function resolveVariableReference(scope, { name }) {

@@ -210,4 +212,4 @@ let arg;

}
else if (scope.args
&& Object.prototype.hasOwnProperty.call(scope.args, name)) {
else if (scope.args &&
Object.prototype.hasOwnProperty.call(scope.args, name)) {
// We're in the top-level Pattern or inside a MessageReference. Missing

@@ -241,3 +243,3 @@ // variables references produce ReferenceErrors.

}
// Resolve a reference to another message.
/** Resolve a reference to another message. */
function resolveMessageReference(scope, { name, attr }) {

@@ -263,3 +265,3 @@ const message = scope.bundle._messages.get(name);

}
// Resolve a call to a Term with key-value arguments.
/** Resolve a call to a Term with key-value arguments. */
function resolveTermReference(scope, { name, attr, args }) {

@@ -289,3 +291,3 @@ const id = `-${name}`;

}
// Resolve a call to a Function with positional and key-value arguments.
/** Resolve a call to a Function with positional and key-value arguments. */
function resolveFunctionReference(scope, { name, args }) {

@@ -312,3 +314,3 @@ // Some functions are built-in. Others may be provided by the runtime via

}
// Resolve a select expression to the member object.
/** Resolve a select expression to the member object. */
function resolveSelectExpression(scope, { selector, variants, star }) {

@@ -328,3 +330,3 @@ let sel = resolveExpression(scope, selector);

}
// Resolve a pattern (a complex string with placeables).
/** Resolve a pattern (a complex string with placeables). */
function resolveComplexPattern(scope, ptn) {

@@ -367,4 +369,6 @@ if (scope.dirty.has(ptn)) {

}
// Resolve a simple or a complex Pattern to a FluentString (which is really the
// string primitive).
/**
* Resolve a simple or a complex Pattern to a FluentString
* (which is really the string primitive).
*/
function resolvePattern(scope, value) {

@@ -380,9 +384,15 @@ // Resolve a simple pattern.

constructor(bundle, errors, args) {
/** The Set of patterns already encountered during this resolution.
* Used to detect and prevent cyclic resolutions. */
/**
* The Set of patterns already encountered during this resolution.
* Used to detect and prevent cyclic resolutions.
* @ignore
*/
this.dirty = new WeakSet();
/** A dict of parameters passed to a TermReference. */
this.params = null;
/** The running count of placeables resolved so far. Used to detect the
* Billion Laughs and Quadratic Blowup attacks. */
/**
* The running count of placeables resolved so far.
* Used to detect the Billion Laughs and Quadratic Blowup attacks.
* @ignore
*/
this.placeables = 0;

@@ -480,3 +490,3 @@ this.bundle = bundle;

...arg.opts,
...values(opts, NUMBER_ALLOWED)
...values(opts, NUMBER_ALLOWED),
});

@@ -486,3 +496,3 @@ }

return new FluentNumber(arg.valueOf(), {
...values(opts, NUMBER_ALLOWED)
...values(opts, NUMBER_ALLOWED),
});

@@ -550,3 +560,3 @@ }

...arg.opts,
...values(opts, DATETIME_ALLOWED)
...values(opts, DATETIME_ALLOWED),
});

@@ -556,3 +566,3 @@ }

return new FluentDateTime(arg.valueOf(), {
...values(opts, DATETIME_ALLOWED)
...values(opts, DATETIME_ALLOWED),
});

@@ -582,30 +592,23 @@ }

*
* The `locales` argument is used to instantiate `Intl` formatters used by
* translations. The `options` object can be used to configure the bundle.
* @example
* ```js
* let bundle = new FluentBundle(["en-US", "en"]);
*
* Examples:
* let bundle = new FluentBundle(locales, {useIsolating: false});
*
* let bundle = new FluentBundle(["en-US", "en"]);
* let bundle = new FluentBundle(locales, {
* useIsolating: true,
* functions: {
* NODE_ENV: () => process.env.NODE_ENV
* }
* });
* ```
*
* let bundle = new FluentBundle(locales, {useIsolating: false});
*
* let bundle = new FluentBundle(locales, {
* useIsolating: true,
* functions: {
* NODE_ENV: () => process.env.NODE_ENV
* }
* });
*
* Available options:
*
* - `functions` - an object of additional functions available to
* translations as builtins.
*
* - `useIsolating` - boolean specifying whether to use Unicode isolation
* marks (FSI, PDI) for bidi interpolations. Default: `true`.
*
* - `transform` - a function used to transform string parts of patterns.
* @param locales - Used to instantiate `Intl` formatters used by translations.
* @param options - Optional configuration for the bundle.
*/
constructor(locales, { functions, useIsolating = true, transform = (v) => v } = {}) {
constructor(locales, { functions, useIsolating = true, transform = (v) => v, } = {}) {
/** @ignore */
this._terms = new Map();
/** @ignore */
this._messages = new Map();

@@ -616,3 +619,3 @@ this.locales = Array.isArray(locales) ? locales : [locales];

DATETIME,
...functions
...functions,
};

@@ -646,18 +649,14 @@ this._useIsolating = useIsolating;

*
* The translation resource must be an instance of `FluentResource`.
* @example
* ```js
* let res = new FluentResource("foo = Foo");
* bundle.addResource(res);
* bundle.getMessage("foo");
* // → {value: .., attributes: {..}}
* ```
*
* let res = new FluentResource("foo = Foo");
* bundle.addResource(res);
* bundle.getMessage("foo");
* // → {value: .., attributes: {..}}
*
* Available options:
*
* - `allowOverrides` - boolean specifying whether it's allowed to override
* an existing message or term with a new value. Default: `false`.
*
* @param res - FluentResource object.
* @param options
* @param res
* @param options
*/
addResource(res, { allowOverrides = false } = {}) {
addResource(res, { allowOverrides = false, } = {}) {
const errors = [];

@@ -696,17 +695,20 @@ for (let i = 0; i < res.body.length; i++) {

*
* let errors = [];
* bundle.addResource(
* new FluentResource("hello = Hello, {$name}!"));
* If `errors` is omitted, the first encountered error will be thrown.
*
* let hello = bundle.getMessage("hello");
* if (hello.value) {
* bundle.formatPattern(hello.value, {name: "Jane"}, errors);
* // Returns "Hello, Jane!" and `errors` is empty.
* @example
* ```js
* let errors = [];
* bundle.addResource(
* new FluentResource("hello = Hello, {$name}!"));
*
* bundle.formatPattern(hello.value, undefined, errors);
* // Returns "Hello, {$name}!" and `errors` is now:
* // [<ReferenceError: Unknown variable: name>]
* }
* let hello = bundle.getMessage("hello");
* if (hello.value) {
* bundle.formatPattern(hello.value, {name: "Jane"}, errors);
* // Returns "Hello, Jane!" and `errors` is empty.
*
* If `errors` is omitted, the first encountered error will be thrown.
* bundle.formatPattern(hello.value, undefined, errors);
* // Returns "Hello, {$name}!" and `errors` is now:
* // [<ReferenceError: Unknown variable: name>]
* }
* ```
*/

@@ -963,3 +965,3 @@ formatPattern(pattern, args = null, errors = null) {

selector,
...variants
...variants,
};

@@ -996,3 +998,3 @@ }

attr,
args: []
args: [],
};

@@ -1029,3 +1031,3 @@ }

name: expr.name,
value: parseLiteral()
value: parseLiteral(),
};

@@ -1068,3 +1070,3 @@ }

type: "str",
value: match1(RE_IDENTIFIER)
value: match1(RE_IDENTIFIER),
};

@@ -1090,3 +1092,3 @@ }

value: parseFloat(value),
precision
precision,
};

@@ -1119,7 +1121,7 @@ }

return codepoint <= 0xd7ff || 0xe000 <= codepoint
// It's a Unicode scalar value.
? String.fromCodePoint(codepoint)
// Lonely surrogates can cause trouble when the parsing result is
// saved using UTF-8. Use U+FFFD REPLACEMENT CHARACTER instead.
: "�";
? // It's a Unicode scalar value.
String.fromCodePoint(codepoint)
: // Lonely surrogates can cause trouble when the parsing result is
// saved using UTF-8. Use U+FFFD REPLACEMENT CHARACTER instead.
"�";
}

@@ -1186,4 +1188,2 @@ throw new SyntaxError("Unknown escape sequence");

Object.defineProperty(exports, '__esModule', { value: true });
}));
{
"name": "@fluent/bundle",
"description": "Localization library for expressive translations.",
"version": "0.17.1",
"version": "0.18.0",
"homepage": "https://projectfluent.org",

@@ -48,3 +48,3 @@ "author": "Mozilla <l10n-drivers@mozilla.org>",

"engines": {
"node": ">=12.0.0",
"node": ">=14.0.0",
"npm": ">=7.0.0"

@@ -51,0 +51,0 @@ },

@@ -6,5 +6,4 @@ # @fluent/bundle ![](https://github.com/projectfluent/fluent.js/workflows/test/badge.svg)

[Project Fluent]: https://projectfluent.org
[project fluent]: https://projectfluent.org
## Installation

@@ -18,3 +17,2 @@

## How to use

@@ -26,3 +24,3 @@

```javascript
import {FluentBundle, FluentResource} from "@fluent/bundle";
import { FluentBundle, FluentResource } from "@fluent/bundle";

@@ -37,3 +35,3 @@ let resource = new FluentResource(`

if (errors.length) {
// Syntax errors are per-message and don't break the whole resource
// Syntax errors are per-message and don't break the whole resource
}

@@ -43,4 +41,4 @@

if (welcome.value) {
bundle.formatPattern(welcome.value, {name: "Anna"});
// → "Welcome, Anna, to Foo 3000!"
bundle.formatPattern(welcome.value, { name: "Anna" });
// → "Welcome, Anna, to Foo 3000!"
}

@@ -51,3 +49,2 @@ ```

## Compatibility

@@ -57,12 +54,12 @@

- `Intl.DateTimeFormat` (standard, well-supported)
- `Intl.NumberFormat` (standard, well-supported)
- `Intl.PluralRules` (standard, new in ECMAScript 2018)
- `Intl.DateTimeFormat` (standard, well-supported)
- `Intl.NumberFormat` (standard, well-supported)
- `Intl.PluralRules` (standard, new in ECMAScript 2018)
`Intl.PluralRules` may already be available in some engines. In most cases,
however, a polyfill will be required. We recommend [intl-pluralrules][].
`Intl.PluralRules` may already be available in some engines. In most cases,
however, a polyfill will be required. We recommend [intl-pluralrules][].
```javascript
import 'intl-pluralrules';
import {FluentBundle} from '@fluent/bundle';
import "intl-pluralrules";
import { FluentBundle } from "@fluent/bundle";
```

@@ -73,2 +70,2 @@

[intl-pluralrules]: https://www.npmjs.com/package/intl-pluralrules
[Compatibility]: https://github.com/projectfluent/fluent.js/wiki/Compatibility
[compatibility]: https://github.com/projectfluent/fluent.js/wiki/Compatibility
SocketSocket SOC 2 Logo

Product

  • Package Alerts
  • Integrations
  • Docs
  • Pricing
  • FAQ
  • Roadmap
  • Changelog

Packages

npm

Stay in touch

Get open source security insights delivered straight into your inbox.


  • Terms
  • Privacy
  • Security

Made with ⚡️ by Socket Inc