Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 8 additions & 4 deletions exploration/percent-format.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
# Formatting Percent Values

Status: **Proposed**
Status: **Accepted**

<details>
<summary>Metadata</summary>
Expand All @@ -11,7 +11,8 @@ Status: **Proposed**
<dt>First proposed</dt>
<dd>2025-04-07</dd>
<dt>Pull Requests</dt>
<dd>#1068</dd>
<dd><a href="https://github.com/unicode-org/message-format-wg/pull/1068">#1068</a></dd>
<dd><a href="https://github.com/unicode-org/message-format-wg/pull/1094">#1094</a></dd>
</dl>
</details>

Expand Down Expand Up @@ -134,9 +135,12 @@ _What prior decisions and existing conditions limit the possible design?_

## Proposed Design

_Describe the proposed solution. Consider syntax, formatting, errors, registry, tooling, interchange._
Add a new, dedicated `:percent` function,
which scales the _resolved value_ of its _operand_ by 100.

TBD
This design could allow a function such as `:unit unit=percent`
for formatting values without scaling
when the working group tackles units.

## Alternatives Considered

Expand Down
1 change: 1 addition & 0 deletions spec/functions/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
1. [`:integer`](number.md#the-integer-function)
1. [`:offset`](number.md#the-offset-function)
1. [`:currency`](number.md#the-currency-function)
1. [`:percent`](number.md#the-percent-function)
1. [`:unit`](number.md#the-unit-function)
1. [Date and Time Value Formatting](datetime.md)
1. [`:datetime`](datetime.md#the-datetime-function)
Expand Down
118 changes: 118 additions & 0 deletions spec/functions/number.md
Original file line number Diff line number Diff line change
Expand Up @@ -413,6 +413,124 @@ contains an implementation-defined currency value
of the _operand_ of the annotated _expression_,
together with the resolved options' values.

#### The `:percent` function

> [!IMPORTANT]
> The _function_ `:percent` has a status of **Draft**.
> It is proposed for inclusion in a future release of this specification and is not Stable.

The function `:percent` is a selector and formatter for percent values.

##### `:percent` Operands

The function `:percent` requires a _numeric operand_ as its _operand_.

When either selecting or formatting the _expression_,
the numeric value of the _operand_ is multiplied by 100.

##### `:percent` Options

Some options do not have default values defined in this specification.
The defaults for these options are implementation-dependent.
In general, the default values for such options depend on the locale,
the value of other options, or both.

> [!NOTE]
> The names of _options_ and their _option values_ were derived from the
> [options](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Intl/NumberFormat/NumberFormat#options)
> in JavaScript's `Intl.NumberFormat`.

The following _options_ are REQUIRED to be available on the function `:percent`:

- `signDisplay`
- `auto` (default)
- `always`
- `exceptZero`
- `negative`
- `never`
- `useGrouping`
- `auto` (default)
- `always`
- `never`
- `min2`
- `minimumFractionDigits`
- _digit size option_, default: `0`
- `maximumFractionDigits`
- _digit size option_, default: `0`
- `minimumSignificantDigits`
- _digit size option_
- `maximumSignificantDigits`
- _digit size option_
- `trailingZeroDisplay`
- `auto` (default)
- `stripIfInteger`
- `roundingPriority`
- `auto` (default)
- `morePrecision`
- `lessPrecision`
- `roundingMode`
- `ceil`
- `floor`
- `expand`
- `trunc`
- `halfCeil`
- `halfFloor`
- `halfExpand` (default)
- `halfTrunc`
- `halfEven`

The numeric value of the _operand_ is multiplied by 100
at the start of formatting or selection.
Each _option_ is applied to the formatted (or selected) value
rather than the unaltered value of the _operand_.

> For example, this _placeholder_:
>
> ```
> {0.1234 :percent maximumFractionDigits=1}
> ```
>
> might be formatted as "12.3%" in an English locale.

If the _operand_ of the _expression_ is an implementation-defined type,
such as the _resolved value_ of an _expression_ with a `:number` or `:integer` _annotation_,
it can include option values.
In general, these are included in the resolved option values of the _expression_,
with _options_ on the _expression_ taking priority over any options of the _operand_.
Options with the following names are however discarded if included in the _operand_:

- `minimumIntegerDigits`
- `roundingIncrement`
- `select`

##### `:percent` Resolved Value

The _resolved value_ of an _expression_ with a `:percent` _function_
contains an implementation-defined numerical value
of the _operand_ of the annotated _expression_
together with the resolved options' values.
The numerical value of the _resolved value_ of the _expression_
is the same as the numerical value of its _operand_;
it is not multiplied by 100.

##### Selection with `:percent`

The _function_ `:percent` performs selection as described in [Number Selection](#number-selection) below.
This selection always uses the `plural` selection mode,
and is performed on the numerical value of the _operand_
multiplied by 100.

> For example, this _message_:
> ```
> .local $pct = {1 :percent}
> .match $pct
> 1 {{Would match with 0.01 as the operand}}
> 100 {{Matches 💯}}
> * {{Otherwise}}
> ```
>
> would be formatted as "Matches 💯".

#### The `:unit` function

> [!IMPORTANT]
Expand Down
15 changes: 9 additions & 6 deletions test/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ These test files are intended to be useful for testing multiple different _messa
> If your implementation uses UTF-16 based strings (such as JavaScript `String` or Java `java.lang.String`)
> or otherwise allows unpaired surrogates in text or literals, you will need to implement tests equivalent
> to the following for syntax errors:
>
> ```json
> {
> "locale": "en-US",
Expand Down Expand Up @@ -72,11 +73,12 @@ Tests for such features have a `tags` array attached to them
to mark the features that they rely on.
This may include one or more of the following:

| Tag | Feature |
| ---------- | ----------------------------------------------------- |
| `u:dir` | The [u:dir](../spec/u-namespace.md#udir) option |
| `u:id` | The [u:id](../spec/u-namespace.md#uid) option |
| `u:locale` | The [u:locale](../spec/u-namespace.md#ulocale) option |
| Tag | Feature |
| ---------- | ------------------------------------------------------------------------- |
| `:percent` | The [:percent](../spec/functions/number.md#the-percent-function) function |
| `u:dir` | The [u:dir](../spec/u-namespace.md#udir) option |
| `u:id` | The [u:id](../spec/u-namespace.md#uid) option |
| `u:locale` | The [u:locale](../spec/u-namespace.md#ulocale) option |

## Test Functions

Expand Down Expand Up @@ -199,8 +201,9 @@ emit a _Bad Option_ error.
> Actual functions in your implementation might emit
> an implementation-defined or platform-specific runtime error or exception
> when the function handler is called.
> Your implementation might thus produce a _Message Function Error_
> Your implementation might thus produce a _Message Function Error_
> not provided with a label in the JSON Schema of this test suite.

### `:test:select`

This _function_ accepts the same _operands_ and _options_,
Expand Down
1 change: 1 addition & 0 deletions test/schemas/v0/tests.schema.json
Original file line number Diff line number Diff line change
Expand Up @@ -204,6 +204,7 @@
"type": "array",
"items": {
"enum": [
":percent",
"u:dir",
"u:id",
"u:locale"
Expand Down
45 changes: 45 additions & 0 deletions test/tests/functions/percent.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
{
"$schema": "../../schemas/v0/tests.schema.json",
"scenario": "Percent function",
"description": "The built-in formatter and selector for percent values.",
"defaultTestProperties": {
"tags": [":percent"],
"bidiIsolation": "none",
"locale": "en-US",
"expErrors": []
},
"tests": [
{
"src": "{:percent}",
"expErrors": [{ "type": "bad-operand" }],
"exp": "{:percent}"
},
{
"src": "{foo :percent}",
"expErrors": [{ "type": "bad-operand" }],
"exp": "{|foo|}"
},
{ "src": "{1 :percent}" },
{ "src": ".local $n = {0.42 :number} {{{$n :percent}}}" },
{ "src": ".local $n = {42 :integer} {{{$n :percent}}}" },
{ "src": ".local $n = {0.01 :percent} {{{$n :percent}}}" },
{ "src": "{0.12345678 :percent}" },
{ "src": "{0.12345678 :percent maximumFractionDigits=1}" },
{ "src": "{0.12 :percent minimumFractionDigits=1}" },
{ "src": "{0.12 :percent minimumSignificantDigits=1}" },
{
"src": "{$x :percent}",
"params": [{ "name": "x", "value": 0.99 }]
},
{
"src": ".input {$n :percent} .match $n one {{one}} * {{other}}",
"params": [{ "name": "n", "value": 0.01 }],
"exp": "one"
},
{
"src": ".input {$n :percent} .match $n one {{one}} * {{other}}",
"params": [{ "name": "n", "value": 1 }],
"exp": "other"
}
]
}