Skip to content
Merged
Show file tree
Hide file tree
Changes from 1 commit
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
10 changes: 6 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,10 @@ _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
Separately, allow `:unit unit=percent` formatting, without scaling.

## 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
101 changes: 101 additions & 0 deletions spec/functions/number.md
Original file line number Diff line number Diff line change
Expand Up @@ -413,6 +413,107 @@ 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.

##### Operands

The function `:percent` requires a [Number Operand](#number-operands) as its _operand_.

When either selecting or formatting the _expression_,
the _resolved value_ of the _operand_ is multiplied by 100.

##### 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`

When formatting or selecting, each of the options is applied
after the _resolved value_ of the _operand_ is multiplied by 100

> For example, this _placeholder_:
>
> ```
> {0.1234 :percent maximumFractionDigits=1}
> ```
>
> would 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`

##### 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.

##### Selection

The _function_ `:percent` performs selection as described in [Number Selection](#number-selection) below,
with selection always using `plural` selection mode,
and with the _resolved value_ of the _operand_ multiplied by 100.

#### 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"
}
]
}