├── .gitattributes ├── .github └── workflows │ └── build.yml ├── .gitignore ├── .npmrc ├── LICENSE ├── README.md ├── index.html ├── package.json └── spec.emu /.gitattributes: -------------------------------------------------------------------------------- 1 | index.html -diff merge=ours 2 | spec.js -diff merge=ours 3 | spec.css -diff merge=ours 4 | -------------------------------------------------------------------------------- /.github/workflows/build.yml: -------------------------------------------------------------------------------- 1 | name: Deploy spec 2 | 3 | on: 4 | push: 5 | paths: 6 | - spec.emu 7 | 8 | jobs: 9 | build: 10 | runs-on: ubuntu-latest 11 | 12 | steps: 13 | - uses: actions/checkout@v4 14 | - uses: actions/setup-node@v4 15 | with: 16 | node-version: '20.x' 17 | - run: npm install 18 | - run: npm run build 19 | - name: commit changes 20 | uses: elstudio/actions-js-build/commit@v3 21 | with: 22 | commitMessage: "fixup: [spec] `npm run build`" 23 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .* 2 | node_modules 3 | package-lock.json 4 | -------------------------------------------------------------------------------- /.npmrc: -------------------------------------------------------------------------------- 1 | package-lock=false 2 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2017 ECMA TC39 and contributors 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Intl.MessageFormat 2 | 3 | ## Status 4 | 5 | Champions: Eemeli Aro (Mozilla/OpenJS Foundation), Ujjwal Sharma (Igalia) 6 | 7 | Former Champions: Daniel Minor (Mozilla) 8 | 9 | ### Stage: 1 10 | 11 | #### Presentations 12 | 13 | - 2022 March: [Stage 1 proposal](https://docs.google.com/presentation/d/1oThTeL_n5-HAfmJTri-i8yU2YtHUvj9AakmWiyRGPlw/edit?usp=sharing) 14 | - 2023 October: [Stage 1 update](https://docs.google.com/presentation/d/15lwZipk0k5pMscSBbEPpMySsnM_qd4MOo_NqmmKyS-Q/edit?usp=sharing) 15 | - 2024 February: [Stage 1 update](https://docs.google.com/presentation/d/15lwZipk0k5pMscSBbEPpMySsnM_qd4MOo_NqmmKyS-Q/edit?usp=sharing) 16 | 17 | ## Motivation 18 | 19 | This proposal aims to make it easier to localize the web, 20 | increasing the openness and accessibility of the web for speakers of all languages. 21 | Currently, localization relies on a collection of mostly proprietary message formatting specifications 22 | that are limited in their features and/or challenging for translators to work with. 23 | Furthermore, localization often relies on parsing these custom formats 24 | during the runtime or rendering of an application. 25 | 26 | To help with this, we introduce 27 | `Intl.MessageFormat` as a native parser and formatter for [MessageFormat 2.0] (aka “MF2”) messages. 28 | MF2 is a specification currently being developed under the Unicode Consortium, with wide industry support. 29 | This will allow for using MF2 messages to localize web sites, 30 | enabling the localization of the web using industry standard tooling and processes. 31 | 32 | In addition to a syntax that is designed to be accessible by both developers and translators, 33 | MF2 defines a message data model that may be used to represent messages defined in any existing syntax. 34 | This enables `Intl.MessageFormat` to be used within existing systems and workflows, 35 | providing a shared message formatting runtime for all users. 36 | 37 | [messageformat 2.0]: https://github.com/unicode-org/message-format-wg/ 38 | 39 | ## Use cases 40 | 41 | The primary use case is the retrieval and resolution of localized text (i.e. a “message”) 42 | given a message source text, the locale and other options, 43 | and optionally a set of runtime values. 44 | 45 | Put together, this allows for any message ranging from the simplest to the most complex 46 | to be defined by a developer, translated to any number of locales, and displayed to a user. 47 | 48 | For instance, consider a relatively simple message such as 49 | 50 | > You have 3 new notifications 51 | 52 | In practice, this would need to account for any number of notifications, 53 | and the plural rules of the current locale. 54 | Using [MF2 syntax], this could be defined as: 55 | 56 | [mf2 syntax]: https://github.com/unicode-org/message-format-wg/blob/main/spec/syntax.md 57 | 58 | ```ini 59 | .match {$count :number} 60 | 0 {{You have no new notifications}} 61 | one {{You have {$count} new notification}} 62 | * {{You have {$count} new notifications}} 63 | ``` 64 | 65 | Some parts of the full message are explicitly repeated for each case, 66 | as this makes it significantly easier for translators to work with the message. 67 | 68 | In code, with the API proposed below, this would be used like this: 69 | 70 | ```js 71 | const source = ... // string source of the message as above 72 | const mf = new Intl.MessageFormat('en', source); 73 | const notifications = mf.format({ count: 1 }); 74 | // 'You have 1 new notification' 75 | ``` 76 | 77 | As a majority of messages do not require multiple variants, 78 | those are of course also supported by the proposed API: 79 | 80 | ```js 81 | // A plain message 82 | const mf1 = new Intl.MessageFormat('en', 'Hello!'); 83 | mf1.format(); // 'Hello!' 84 | 85 | // A parametric message, formatted to parts 86 | const mf2 = new Intl.MessageFormat('en', 'Hello {$place}!'); 87 | const greet = mf2.formatToParts({ place: 'world' }); 88 | /* [ 89 | { type: 'text', value: 'Hello ' }, 90 | { type: 'string', source: '$place', value: 'world' }, 91 | { type: 'text', value: '!' } 92 | ] */ 93 | ``` 94 | 95 | More complex use cases and usage patterns are described within the API description. 96 | 97 | ## API Description 98 | 99 | Though the MF2 specification is still being developed by the working group, 100 | the API presented here is representative of its current consensus. 101 | In particular, the exact shape of `MessageData` is still being discussed in the working group. 102 | 103 | This proposal introduces one new primordial to ECMAScript, `Intl.MessageFormat`. 104 | The other `interface` descriptions below are intended to represent plain objects. 105 | 106 | ### MessageData 107 | 108 | The `MessageData` interface is defined by the 109 | [MF2 data model](https://github.com/unicode-org/message-format-wg/tree/main/spec/data-model) 110 | developed by the Unicode MessageFormat working group. 111 | It contains a parsed representation of a single message for a particular locale. 112 | 113 | ```ts 114 | type MessageData = PatternMessage | SelectMessage; 115 | 116 | interface PatternMessage { 117 | type: 'message'; 118 | declarations: Declaration[]; 119 | pattern: Pattern; 120 | } 121 | 122 | interface SelectMessage { 123 | type: 'select'; 124 | declarations: Declaration[]; 125 | selectors: Expression[]; 126 | variants: Variant[]; 127 | } 128 | ``` 129 | 130 | The full and exact definition of the message data model is given by its 131 | [JSON Schema definition](https://github.com/unicode-org/message-format-wg/blob/main/spec/data-model/message.json). 132 | 133 | ### MessageFormat 134 | 135 | The `Intl.MessageFormat` constructor creates a `MessageFormat` instance 136 | for a `source` message, one or more locale identifiers, 137 | and an optional `MessageFormatOptions` object. 138 | If a string is used as the `source` argument, 139 | it will be parsed as a MF2 syntax representation of a message. 140 | 141 | Calling the constructor may throw an error if the `source` includes an MF2 142 | [syntax or data model error](https://github.com/unicode-org/message-format-wg/blob/main/spec/formatting.md#error-handling). 143 | 144 | ```ts 145 | interface MessageFormat { 146 | new ( 147 | locales: string | string[] | undefined, 148 | source: MessageData | string, 149 | options?: MessageFormatOptions 150 | ): MessageFormat; 151 | 152 | format( 153 | values?: Record, 154 | onError?: (error: Error) => void 155 | ): string; 156 | 157 | formatToParts( 158 | values?: Record, 159 | onError?: (error: Error) => void 160 | ): MessagePart[]; 161 | 162 | resolvedOptions(): ResolvedMessageFormatOptions; 163 | } 164 | ``` 165 | 166 | #### Constructor options and resolvedOptions() 167 | 168 | `MessageFormatOptions` contains configuration options 169 | for the creation of `MessageFormat` instances. 170 | The `ResolvedMessageFormatOptions` object contains the options 171 | resolved during the construction of the `MessageFormat` instance. 172 | 173 | As messages may contain placeholders resolving to strings with different directionality 174 | than the message as a whole (as in, left-to-right vs. right-to-left), 175 | the `bidiIsolation` option defines a strategy 176 | by which these parts will be isolated from each other in the output to avoid spillover effects. 177 | The default `'compatibility'` strategy will include Unicode isolate code points 178 | at the boundaries of all expressions that are not known to match the message's directionality. 179 | The `'none'` strategy will not provide any bidirectional isolation. 180 | 181 | By default the message's directionality is determined from 182 | the script corresponding to the first locale, 183 | but this may be overridden by `dir`. 184 | Its `"auto"` value corresponds to messages with unknown directionality, 185 | for which the direction is determined by the first strongly directional character. 186 | 187 | Custom user-defined message formatting and selection functions may defined by the `functions` option. 188 | These allow for any data types to be handled by custom functions. 189 | Such functions may be referenced within messages, 190 | and then called with the resolved values of their arguments and options. 191 | 192 | ```ts 193 | interface MessageFormatOptions { 194 | bidiIsolation?: 'compatibility' | 'none'; 195 | dir?: 'ltr' | 'rtl' | 'auto'; 196 | functions?: { [key: string]: MessageFunction }; 197 | localeMatcher?: 'best fit' | 'lookup'; 198 | } 199 | 200 | interface ResolvedMessageFormatOptions { 201 | bidiIsolation: 'compatibility' | 'none'; 202 | dir: 'ltr' | 'rtl' | 'auto'; 203 | functions: { [key: string]: MessageFunction }; 204 | localeMatcher: 'best fit' | 'lookup'; 205 | } 206 | ``` 207 | 208 | #### format(values?, onError?) 209 | 210 | As with other `Intl` formatters, `format()` returns a string. 211 | This method has the following optional arguments: 212 | 213 | - `values` provides variable values for the message's variable references. 214 | - `onError` defines an error handler that will be called if 215 | message resolution or formatting fails. 216 | If `onError` is not defined, 217 | a warning will be issued for each error and a [fallback representation] used for the corresponding message part. 218 | 219 | To determine the value `res` returned by the `format()` method, 220 | the message is first resolved to a list of MessageValue instances. 221 | Starting with an empty string `res`, for each MessageValue `mv`: 222 | 223 | 1. Let `msgDir` be the base direction of the message. 224 | 1. Let `bidiIsolation` be the resolved value of the `bidiIsolation` option. 225 | 1. Let `dir` be `mv.dir`. 226 | 1. Let `strval` be the result of calling `mv.toString()`. 227 | 1. If the call fails or `strval` is not a string: 228 | 1. Set `strval` to be the concatenation of `{`, `mv.source`, and `}`. 229 | 1. Set `dir` to be `"auto"`. 230 | 1. Let `bidi` be the `{ start: string, end: string }` result of calling 231 | `ApplyBidiIsolation(bidiIsolation, msgDir, dir)`. 232 | 1. Append `bidi.start`, `strval`, and `bidi.end` to the end of `res`. 233 | 234 | The ApplyBidiIsolation abstract operation will take as arguments 235 | the current bidi isolation strategy and the message and part directions. 236 | From these it will determine `start` and `end` as sequences of Unicode code points 237 | which will, if necessary, isolate parts from each other. 238 | With the default "compatibility" strategy, the result matches this TS type: 239 | 240 | ```ts 241 | type BidiIsolation = 242 | | { start: ''; end: '' } 243 | | { 244 | start: '\u2066' | '\u2067' | '\u2068'; // LRI | RLI | FSI 245 | end: '\u2069'; // PDI 246 | }; 247 | ``` 248 | 249 | #### formatToParts(values?, onError?) 250 | 251 | For formatting a message to non-string targets, 252 | the `formatToParts()` method is provided, returning an array of `MessagePart` objects. 253 | This method has the following optional arguments: 254 | 255 | - `values` provides variable values for the message's variable references. 256 | - `onError` defines an error handler that will be called if 257 | message resolution or formatting fails. 258 | If `onError` is not defined, 259 | a warning will be issued for each error and a [fallback representation] used for the corresponding message part. 260 | 261 | To determine the value `res` returned by the `formatToParts()` method, 262 | the message is first resolved to a list of MessageValue instances. 263 | Starting with an empty array `res`, for each MessageValue `mv`: 264 | 265 | 1. Let `msgDir` be the base direction of the message. 266 | 1. Let `bidiIsolation` be the resolved value of the `bidiIsolation` option. 267 | 1. Let `dir` be `mv.dir`. 268 | 1. Let `parts` be the result of calling `mv.toParts()`. 269 | 1. If the call fails or `parts` is not an array: 270 | 1. Set `parts` to be `[{ type: "fallback", source: mv.source }]`. 271 | 1. Set `dir` to be `"auto"`. 272 | 1. Let `bidi` be the `{ start: string, end: string }` result of calling 273 | `ApplyBidiIsolation(bidiIsolation, msgDir, dir)`. 274 | 1. If `bidi.start` is not an empty string: 275 | 1. Append `{ type: 'bidiIsolation', value: bidi.start }` to `res`. 276 | 1. For each `part` or `parts`: 277 | 1. Append `part` to `res`. 278 | 1. If `bidi.end` is not an empty string: 279 | 1. Append `{ type: 'bidiIsolation', value: bidi.end }` to `res`. 280 | 281 | [fallback representation]: https://github.com/unicode-org/message-format-wg/blob/main/spec/formatting.md#fallback-resolution 282 | 283 | ### MessageValue 284 | 285 | When formatting a message, 286 | the selectors and placeholders of a message are each resolved first 287 | to an intermediate `MessageValue` representation. 288 | This can be thought of as an immutable object with properties and methods, 289 | though its JavaScript representation is only available to custom functions. 290 | 291 | ```ts 292 | interface MessageValue { 293 | type: string; 294 | locale: string; 295 | dir: 'ltr' | 'rtl' | 'auto'; 296 | source: string; 297 | options?: { [key: string]: unknown }; 298 | selectKeys?: (keys: string[]) => string[]; 299 | toParts?: () => MessagePart[]; 300 | toString?: () => string; 301 | valueOf?: () => unknown; 302 | } 303 | 304 | type MessagePart = 305 | | { type: 'text'; value: string } 306 | | { 307 | type: 'bidiIsolation'; 308 | value: '\u2066' | '\u2067' | '\u2068' | '\u2069'; // LRI | RLI | FSI | PDI 309 | } 310 | | ({ 311 | type: string; 312 | source: string; 313 | locale?: string; 314 | dir?: 'ltr' | 'rtl' | 'auto'; 315 | } & ( 316 | | { value?: unknown } 317 | | { parts: Array<{ type: string; value: unknown; source?: string }> } 318 | )); 319 | ``` 320 | 321 | A `MessageValue` is an object with a string `type`, a string `locale` identifier, 322 | and an opaque `source` string identifying its origin. 323 | All other fields are optional; 324 | they determine how the value may be used in MF2 expressions. 325 | 326 | In order to be usable as a formatted placeholder, 327 | the object (or its prototype chain) MUST include a `toString` method returning a string 328 | and a `toParts` method returning an array of `MessagePart`s. 329 | All of the built-in implementations of this method return an array with exactly one value, 330 | but user-defined functions may return any number of parts, or none. 331 | 332 | Except for parts corresponding to literal values, 333 | each `MessagePart` MUST include the `type` and `source` from the `MessageValue`. 334 | It MAY also include a string `locale` identifier, 335 | and optionally either an explicit `value` of any type 336 | or its own sequence of `parts`. 337 | 338 | In order to be usable as a variant selector, 339 | the `MessageValue` object MUST include a `selectKeys` method. 340 | When called with an array of string keys, 341 | it MUST return an array whose elements are a subset of those keys. 342 | The returned keys will be considered to match the selector and be in preferential order. 343 | 344 | Within a message, the value of an expression may be assigned to a message-local variable, 345 | and such a variable may be used as an input argument to another expression, 346 | or as an option value. 347 | Some functions (including the default `number`) accept 348 | objects with a `valueOf` method and an `options` field 349 | in addition to `type` and `locale` as input. 350 | These MAY also be defined on the returned object. 351 | 352 | #### Literal Text 353 | 354 | Text in patterns outside expressions is always literal. 355 | While its resolved value is never presented as JS, 356 | for the sake of simplicity it may be thought of as having the following resolved value: 357 | 358 | ```ts 359 | interface MessageText { 360 | type: 'text'; 361 | source: string; 362 | locale: string; 363 | dir: 'ltr' | 'rtl' | 'auto'; 364 | toParts(): [MessageTextPart]; 365 | toString(): string; 366 | } 367 | 368 | interface MessageTextPart { 369 | type: 'text'; 370 | value: string; 371 | } 372 | ``` 373 | 374 | For `MessageText`, the value returned by `toString()` and 375 | the `value` field of the object returned by `toParts()` 376 | corresponding to the text source. 377 | Its `locale` is always the same as the message's base locale. 378 | 379 | #### Expressions 380 | 381 | Expressions are used as selectors and as pattern placeholders. 382 | A local variable declaration may assign the value of an expression to a local variable, 383 | allowing for the same expression to be used in multiple places 384 | and potentially with different roles. 385 | 386 | An expression may have one of three forms: 387 | 388 | - An operand (either a literal value or a variable reference). 389 | - An operand with an annotation. 390 | - An annotation with no operand. 391 | 392 | The resolution of annotations using the `:` prefix is customisable 393 | using the constructor's `functions` option, 394 | which takes `MessageFunction` function values that are applied when 395 | the annotation's name (without the `:`) corresponds to the `functions` key. 396 | 397 | #### Markup 398 | 399 | In addition to expressions, placeholders may also be markup; 400 | content corresponding to HTML elements or other markup syntax. 401 | Unlike expressions, markup does not accept a positional input argument 402 | and its resolution is not customizable by the `functions` option. 403 | 404 | Markup placeholders take three different forms: 405 | 406 | - "standalone" markup for non-textual content such as inline images, 407 | - "open" markup that starts a markup span, and 408 | - "close" markup that end a markup span. 409 | 410 | The syntax used by markup is somewhat similar to that of XML, 411 | though with curly braces `{}` instead of angle brackets `<>` and 412 | with `#` as a prefix for "standalone" and "open": `{#img /}`, `{#b}`, `{/b}`. 413 | 414 | Markup placeholders are not required to be paired or nest cleanly; 415 | within the formatter each is only considered by itself, 416 | and any higher-level validation is the responsibility of the caller. 417 | 418 | A markup placeholder cannot be used as a selector. 419 | In `format()`, all markup is ignored, with each being formatted to an empty string. 420 | In `formatToParts()`, each markup placeholder is formatted to a single part: 421 | 422 | ```ts 423 | interface MessageMarkupPart { 424 | type: 'markup'; 425 | kind: 'open' | 'standalone' | 'close'; 426 | source: string; 427 | name: string; 428 | options?: { [key: string]: unknown }; 429 | } 430 | ``` 431 | 432 | The `type` of the part is always `"markup"`, 433 | and its `kind` is one of `"open"`, `"standalone"`, or `"close"`. 434 | The `name` matches the name of the markup, 435 | without the `#` or `/` prefixes or suffixes. 436 | The `source` matches the `name` of the markup placeholder, 437 | prefixed and suffixed with the appropriate `#` and `/` characters. 438 | 439 | The `options` correspond to the resolved literal and variable values 440 | of the options included in the placeholder. 441 | For example, when formatting `{#open foo=42 bar=$baz}` with `formatToParts({ baz: 13 })`, 442 | the formatted part's `options` would be `{ foo: '42', bar: 13 }`. 443 | For options with variable reference values, 444 | if the resolved value is an object with a `valueOf()` method, the returned value is used. 445 | The `options` are only supported for "open" and "standalone" markup placeholders 446 | and are never included for a "close" markup placeholder. 447 | 448 | ### MessageFunction 449 | 450 | Fundamentally, messages are formed by concatenating values together. 451 | In order to support the handling of user-defined value types with user-defined formatting options, 452 | as well as other needs, 453 | user-provided message functions may be provided via the constructor's `functions` option 454 | to complement or replace the default ones. 455 | 456 | ```ts 457 | type MessageFunction = ( 458 | msgCtx: MessageFunctionContext, 459 | options: { [key: string]: unknown }, 460 | input?: unknown 461 | ) => MessageValue; 462 | 463 | interface MessageFunctionContext { 464 | locales: string[]; 465 | dir: 'ltr' | 'rtl' | 'auto'; 466 | source: string; 467 | } 468 | ``` 469 | 470 | The `msgCtx` value defines the context in which the expression is being resolved, 471 | with the `locales` and `dir` of the whole message 472 | as well as the `source` fallback string representation of the expression. 473 | 474 | The `input` and `options` values are constructed as follows: 475 | 476 | - If the value is a literal defined in the message syntax, 477 | the value is its `string` value. 478 | - If the value is a variable referring to a local variable declaration, 479 | the value is the `MessageValue` that the declaration's expression resolves to. 480 | - Otherwise, the value is a variable referring to an external value, 481 | and its type and value are that of the external value. 482 | 483 | As function options are often set by literal values, 484 | and as MF2 considers all literals to be strings, 485 | the JSON string representation should be supported for numerical and boolean values 486 | when used as an input or as an option value. 487 | Each function will need to parse these separately from their string representations. 488 | 489 | If a function is locale-dependent, 490 | it should accept an extra option key called `locale` 491 | which resolves to a string overriding the message's base locale. 492 | This option value should always be parsed as an array or 493 | (if a single string) a comma-delimited list of BCP 47 locale identifiers. 494 | 495 | #### Default Functions 496 | 497 | Two commonly used message functions `number` and `string` are provided as a starting point, 498 | and as handlers for placeholders without an annotation, 499 | such as variable references like `{$foo}` or literal values like `{|the bar|}`. 500 | 501 | Variable references are resolved by first looking for a local variable declaration 502 | matching its name, then by looking in the `values` argument. 503 | Literal values always resolve to strings. 504 | 505 | As selector expressions must have an annotation or contain a variable reference 506 | that references a variable declaration within the same message with an annotation, 507 | un-annotated expressions only need to be considered for formattable placeholders. 508 | 509 | If a placeholder expression contains a variable reference without an annotation 510 | and the variable resolves to a number or bigint value or a Number instance, 511 | it resolves instead to the result of calling the `number` function 512 | with the numerical value as input and no options. 513 | 514 | If a placeholder expression would resolve to a string value or a String instance, 515 | it resolves instead to the result of calling the `string` function 516 | with the value as input and no options. 517 | 518 | Otherwise, un-annotated values resolve to the following shape: 519 | 520 | ```ts 521 | interface MessageUnknownValue { 522 | type: 'unknown'; 523 | source: string; 524 | locale: string; 525 | dir: 'ltr' | 'rtl' | 'auto'; 526 | toParts(): [MessageUnknownPart]; 527 | toString(): string; 528 | valueOf(): unknown; 529 | } 530 | 531 | interface MessageUnknownPart { 532 | type: 'unknown'; 533 | source: string; 534 | value: unknown; 535 | } 536 | ``` 537 | 538 | With `MessageUnknownValue`, 539 | the `toString()` method uses the equivalent of `String()` to format the value 540 | while `valueOf()` returns the original value. 541 | 542 | If the constructor options of a MessageFormat instance include a `functions` value 543 | that overrides the default `number` or `string` functions, 544 | those will be called instead of the default ones, as appropriate. 545 | 546 | #### `number` 547 | 548 | Accepts as input any of the following: 549 | 550 | - A number or a bigint. 551 | This is used directly as the `value`. 552 | - An object with a `valueOf()` method that returns a number or a bigint, 553 | which is then used as the `value`. 554 | - The JSON string representation of a number, to accommodate literal values as in `{42 :number}`. 555 | The `value` is determined by the equivalent of calling `JSON.parse()` on the string, 556 | and asserting that it returns a number or a bigint. 557 | 558 | Returns a fallback value if invoked without such an input, 559 | or if an error occurs while determining `value`. 560 | 561 | Internally, constructs a `locales` array of strings as used by 562 | the Intl.NumberFormat and Intl.PluralRules constructors. 563 | This will include, in order: 564 | 565 | 1. Any locales set by the expression's `"locale"` option. 566 | 2. The locale of the input, if it is an object and has a string or string array property `"locale"`. 567 | 3. The base locale or locale chain of the message. 568 | 569 | To determine the formatting and selection options for the number, 570 | a new empty `options` object is created. 571 | If the input is an object with a property `"options"` with an object value, 572 | the `options` object is extended with its values. 573 | Then, a primitive value is set for each of the expression's options except `"locale"`: 574 | 575 | - For each option value, if it is an object, coerce it to a string. 576 | - For `useGrouping`, convert `"true"` and `"false"` to their corresponding boolean values. 577 | - For `roundingIncrement`, `minimumIntegerDigits`, and `(minimum|maximum)(Fraction|Significant)Digits`, 578 | parse a string value as a non-negative integer number. 579 | 580 | Returns a value with the following shape: 581 | 582 | ```ts 583 | interface MessageNumber { 584 | type: 'number'; 585 | source: string; 586 | locale: string; 587 | dir: 'ltr' | 'rtl' | 'auto'; 588 | options: Intl.NumberFormatOptions & Intl.PluralRulesOptions; 589 | selectKeys(keys: string[]): string[]; 590 | toParts(): [MessageNumberPart]; 591 | toString(): string; 592 | valueOf(): number | bigint; 593 | } 594 | 595 | interface MessageNumberPart { 596 | type: 'number'; 597 | source: string; 598 | locale?: string; 599 | dir?: 'ltr' | 'rtl' | 'auto'; 600 | parts: Intl.NumberFormatPart[]; 601 | } 602 | ``` 603 | 604 | When a `MessageNumber` is used as a selector (calling its `selectKeys()` method), 605 | a key with an exact numeric match to the value will be preferred over a key matching the value's plural category 606 | (some of `zero`, `one`, `two`, `few`, `many`, and `other`, depending on the locale). 607 | 608 | When a `MessageNumber` is being formatted, 609 | calling its `toString()` will return a string corresponding to calling 610 | 611 | ```js 612 | new Intl.NumberFormat(locales, options).format(value); 613 | ``` 614 | 615 | and calling its `toParts()` method will return an array with a single object member 616 | where the `parts` will correspond to the results of calling 617 | 618 | ```js 619 | new Intl.NumberFormat(locales, options).formatToParts(value); 620 | ``` 621 | 622 | #### `string` 623 | 624 | Accepts any input, and parses any non-string value using `String()`. 625 | For no input, resolves its value to an empty string. 626 | On error, resolves to a fallback value. 627 | 628 | Accepts only the `locale` option as an override for the message's locale. 629 | 630 | Returns a value with the following shape: 631 | 632 | ```ts 633 | interface MessageString { 634 | type: 'string'; 635 | source: string; 636 | locale: string; 637 | dir: 'ltr' | 'rtl' | 'auto'; 638 | selectKeys(keys: string[]): [] | [string]; 639 | toParts(): [MessageStringPart]; 640 | toString(): string; 641 | valueOf(): string; 642 | } 643 | 644 | interface MessageStringPart { 645 | type: 'string'; 646 | source: string; 647 | locale?: string; 648 | dir?: 'ltr' | 'rtl' | 'auto'; 649 | value: string; 650 | } 651 | ``` 652 | 653 | When a `MessageString` is used as a selector, 654 | the returned array may include at most one entry, 655 | if one of the keys was an exact string match for the value. 656 | 657 | ### Fallback Values 658 | 659 | It's possible for a `MessageFunction` call to throw an error, 660 | or for a `format()` or `formatToParts()` call to throw an error. 661 | When this happens, 662 | the error is caught and a user-provided `onError` handler is called. 663 | If no such handler is defined, 664 | the default behaviour is to issue a warning rather than throwing the error. 665 | This allows for some formatted representation to always be provided. 666 | 667 | In such a case, a fallback representation is used instead for the value: 668 | 669 | ```ts 670 | interface MessageFallback { 671 | type: 'fallback'; 672 | locale: 'und'; 673 | dir: 'auto'; 674 | source: string; 675 | toParts(): [MessageFallbackPart]; 676 | toString(): string; 677 | } 678 | 679 | interface MessageFallbackPart { 680 | type: 'fallback'; 681 | source: string; 682 | } 683 | ``` 684 | 685 | This representation is also used when resolving MF2 expressions 686 | that include "reserved" or "private-use" annotations. 687 | 688 | The `source` of the `MessageFallback` corresponds to the `source` of the `MessageValue`. 689 | When `MessageFallback` is formatted to a string, 690 | its value is the concatenation of a left curly brace `{`, the `source` value, 691 | and a right curly brace `}`. 692 | 693 | ## Comparison 694 | 695 | The MF2 specification is being developed based upon lessons learned from existing systems 696 | including [ICU MessageFormat] and [Fluent]. 697 | 698 | The implementation of Fluent within Firefox mostly relies upon 699 | a declarative syntax in the DOM, 700 | but it does provide an [API] for retrieving messages directly from Fluent 701 | when not being used to localize the DOM. 702 | 703 | [icu messageformat]: https://unicode-org.github.io/icu/userguide/format_parse/messages/ 704 | [fluent]: https://projectfluent.org/ 705 | [api]: https://firefox-source-docs.mozilla.org/l10n/fluent/tutorial.html#non-markup-localization 706 | 707 | ## Implementations 708 | 709 | ### Polyfill/transpiler implementations 710 | 711 | The MessageFormat 2.0 specification is under development. 712 | A polyfill implementation of this proposal is available under the 713 | [messageformat](https://github.com/messageformat/messageformat/tree/master/packages/mf2-messageformat) project. 714 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "private": true, 3 | "name": "template-for-proposals", 4 | "description": "A repository template for ECMAScript proposals.", 5 | "scripts": { 6 | "start": "npm run build-loose -- --watch", 7 | "build": "npm run build-loose -- --strict", 8 | "build-loose": "ecmarkup --verbose spec.emu index.html" 9 | }, 10 | "homepage": "https://github.com/tc39/template-for-proposals#readme", 11 | "repository": { 12 | "type": "git", 13 | "url": "git+https://github.com/tc39/template-for-proposals.git" 14 | }, 15 | "license": "MIT", 16 | "devDependencies": { 17 | "ecmarkup": "^18.1.1" 18 | }, 19 | "prettier": { 20 | "singleQuote": true, 21 | "trailingComma": "none" 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /spec.emu: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 11 | 12 | 13 |

MessageFormat Objects

14 | 15 | 16 |

The Intl.MessageFormat Constructor

17 | 18 |

19 | The MessageFormat constructor is the %MessageFormat% intrinsic object 20 | and a standard built-in property of the Intl object. 21 | Behaviour common to all service constructor properties of the Intl object 22 | is specified in 9.1. 23 |

24 | 25 | 26 |

Intl.MessageFormat ( _locales_, _source_ [ , _options_ ] )

27 | 28 |

29 | When the `Intl.MessageFormat` function is called with arguments 30 | _locales_, _source_, and _options_, the following steps are taken: 31 |

32 | 33 | 34 | 1. If NewTarget is *undefined*, throw a *TypeError* exception. 35 | 1. Let _messageFormat_ be ? OrdinaryCreateFromConstructor(NewTarget, %MessageFormat.prototype%, 36 | « [[InitializedMessageFormat]], [[LocaleMatcher]], [[MessageData]], [[RequestedLocales]], [[Runtime]] »). 37 | 1. Return ? InitializeMessageFormat(_messageFormat_, _locales_, _source_, _options_). 38 | 39 |
40 | 41 | 42 |

InitializeMessageFormat ( _messageFormat_, _locales_, _source_, _options_ )

43 | 44 |

45 | The abstract operation InitializeMessageFormat accepts the arguments 46 | _messageFormat_ (which must be an object), _locales_, _source_, and _options_. 47 | It initializes _messageFormat_ as a MessageFormat object. 48 | The following steps are taken: 49 |

50 | 51 | 52 | 1. Let _requestedLocales_ be ? CanonicalizeLocaleList(_locales_). 53 | 1. If _source_ is *undefined*, throw a *TypeError* exception. 54 | 1. Let _msgData_ be ? GetMessageData(_source_). 55 | 1. Set _options_ to ? GetOptionsObject(_options_). 56 | 1. Let _matcher_ be ? GetOption(_options_, *"localeMatcher"*, ~string~, 57 | « *"lookup"*, *"best fit"* », *"best fit"*). 58 | 1. Let _userFunctions_ be ? Get(_options_, *"functions"*). 59 | 1. Let _functions_ be ? GetMessageFunctions(_userFunctions_). 60 | 1. Set _messageFormat_.[[MessageData]] to _msgData_. 61 | 1. Set _messageFormat_.[[RequestedLocales]] to _requestedLocales_. 62 | 1. Set _messageFormat_.[[LocaleMatcher]] to _matcher_. 63 | 1. Set _messageFormat_.[[Functions]] to _functions_. 64 | 1. Return _messageFormat_. 65 | 66 |
67 |
68 | 69 | 70 |

Properties of the Intl.MessageFormat Constructor

71 | 72 |

73 | The Intl.MessageFormat constructor has the following properties: 74 |

75 | 76 | 77 |

Intl.MessageFormat.prototype

78 | 79 |

80 | The value of `Intl.MessageFormat.prototype` is %MessageFormat.prototype%. 81 |

82 |

83 | This property has the attributes 84 | { [[Writable]]: *false*, [[Enumerable]]: *false*, [[Configurable]]: *false* }. 85 |

86 |
87 | 88 | 89 |

Internal slots

90 | 91 | 92 | Unlike other Intl formatters, 93 | MessageFormat does not have a single list of locales that it supports, 94 | as it calls other formatters as necessary. 95 | 96 |
97 |
98 | 99 | 100 |

Properties of the Intl.MessageFormat Prototype Object

101 | 102 |

103 | The Intl.MessageFormat prototype object is itself an ordinary object. 104 | %MessageFormat.prototype% is not an Intl.MessageFormat instance 105 | and does not have an [[InitializedMessageFormat]] internal slot 106 | or any of the other internal slots of Intl.MessageFormat instance objects. 107 |

108 | 109 | 110 |

Intl.MessageFormat.prototype.constructor

111 | 112 |

113 | The initial value of `Intl.MessageFormat.prototype.constructor` is %MessageFormat%. 114 |

115 |
116 | 117 | 118 |

Intl.MessageFormat.prototype [ @@toStringTag ]

119 | 120 |

121 | The initial value of the @@toStringTag property is the String value *"Intl.MessageFormat"*. 122 |

123 |

124 | This property has the attributes 125 | { [[Writable]]: *false*, [[Enumerable]]: *false*, [[Configurable]]: *true* }. 126 |

127 |
128 | 129 | 130 |

Intl.MessageFormat.prototype.format ( [ _values_ [ , _onError_ ] ] )

131 | 132 |

133 | When the `format` method is called with the optional arguments 134 | _values_ and _onError_, the following steps are taken: 135 |

136 | 137 | 138 | 1. Let _mf_ be the *this* value. 139 | 1. Perform ? RequireInternalSlot(_mf_, [[InitializedMessageFormat]]). 140 | 1. Let _ctx_ be ? CreateMessageFormatContext(_mf_, _values_, _onError_). 141 | 1. Let _msg_ be ? ResolveMessage(_ctx_). 142 | 1. Let _result_ be an empty String. 143 | 1. For each element _el_ of _msg_, do 144 | 1. If Type(_el_) is String, then 145 | 1. Let _stringValue_ be _el_. 146 | 1. Else if Type(_el_) is Object and _el_ has a [[MessageValue]] internal slot, then 147 | 1. Let _mv_ be _el_.[[MessageValue]]. 148 | 1. Assert: Type(_mv_) is Object. 149 | 1. Let _toString_ be ? Get(_mv_, *"toString"*). 150 | 1. Let _toStringResult_ be Completion(Call(_toString_, _mv_)). 151 | 1. If _toStringResult_ is a normal completion, then 152 | 1. Let _stringValue_ be _toStringResult_.[[Value]]. 153 | 1. Else, 154 | 1. Perform ? HandleMessageFormatError(_ctx_, _toStringResult_). 155 | 1. Let _source_ be MessageValueSource(_mv_). 156 | 1. Let _stringValue_ be MessageFallbackString(_source_). 157 | 1. Set _result_ to the string-concatenation of _result_ and _stringValue_. 158 | 1. Return _result_. 159 | 160 |
161 | 162 | 163 |

Intl.MessageFormat.prototype.formatToParts ( [ _values_ [ , _onError_ ] ] )

164 | 165 |

166 | When the `formatToParts` method is called with the optional arguments 167 | _values_ and _onError_, the following steps are taken: 168 |

169 | 170 | 171 | 1. Let _mf_ be the *this* value. 172 | 1. Perform ? RequireInternalSlot(_mf_, [[InitializedMessageFormat]]). 173 | 1. Let _ctx_ be ? CreateMessageFormatContext(_mf_, _values_, _onError_). 174 | 1. Let _msg_ be ? ResolveMessage(_ctx_). 175 | 1. Let _result_ be a new empty List. 176 | 1. For each element _el_ of _msg_, do 177 | 1. If Type(_el_) is String, then 178 | 1. Let _textPart_ be OrdinaryObjectCreate(%Object.prototype%). 179 | 1. Perform ! CreateDataPropertyOrThrow(_textPart_, *"type"*, *"text"*). 180 | 1. Perform ! CreateDataPropertyOrThrow(_textPart_, *"value"*, _el_). 181 | 1. Append _textPart_ to _result_. 182 | 1. Else if Type(_el_) is Object and _el_ has a [[MessageMarkup]] internal slot, then 183 | 1. Let _markup_ be ? FormatMarkupPart(_ctx_, _el_.[[MessageMarkup]]). 184 | 1. Append _markup_ to _result_ 185 | 1. Else, 186 | 1. Assert: _el_ has a [[MessageValue]] internal slot. 187 | 1. Let _mv_ be _el_.[[MessageValue]]. 188 | 1. Assert: Type(_mv_) is Object. 189 | 1. Let _toParts_ be ? Get(_mv_, *"toParts"*). 190 | 1. Let _partResult_ be Completion(Call(_toParts_, _mv_)). 191 | 1. If _partResult_ is a normal completion, then 192 | 1. Let _parts_ be _partResult_.[[Value]]. 193 | 1. For each element _part_ of _parts_, append _part_ to _result_. 194 | 1. Else, 195 | 1. Perform ? HandleMessageFormatError(_ctx_, _partResult_). 196 | 1. Let _source_ be MessageValueSource(_mv_). 197 | 1. Let _fallbackPart_ be MessageFallbackPart(_source_). 198 | 1. Append _fallbackPart_ to _result_. 199 | 1. Return CreateArrayFromList(_result_). 200 | 201 |
202 | 203 | 204 |

Intl.MessageFormat.prototype.resolvedOptions ( )

205 | 206 |

207 | This function provides access to the locale and options 208 | computed during initialization of the object. 209 |

210 | 211 | 212 | 1. Let _mf_ be the *this* value. 213 | 1. Perform ? RequireInternalSlot(_mf_, [[InitializedMessageFormat]]). 214 | 1. Let _options_ be OrdinaryObjectCreate(%Object.prototype%). 215 | 1. For each row of , 216 | except the header row, in table order, do 217 | 1. Let _p_ be the Property value of the current row. 218 | 1. Let _v_ be the value of _mf_'s internal slot 219 | whose name is the Internal Slot value of the current row. 220 | 1. If _v_ is not *undefined*, then 221 | 1. Perform ! CreateDataPropertyOrThrow(_options_, _p_, _v_). 222 | 1. Return _options_. 223 | 224 | 225 | 226 | Resolved Options of MessageFormat Instances 227 | 228 | 229 | 230 | 231 | 232 | 233 | 234 | 235 | 236 | 237 | 238 | 239 | 240 | 241 | 242 |
Internal SlotProperty
[[Functions]]*"functions"*
[[LocaleMatcher]]*"localeMatcher"*
243 |
244 |
245 |
246 | 247 | 248 |

Properties of Intl.MessageFormat Instances

249 | 250 |

251 | Intl.MessageFormat instances are ordinary objects that inherit properties from %MessageFormat.prototype%. 252 |

253 | 254 |

255 | Intl.MessageFormat instances have an [[InitializedMessageFormat]] internal slot. 256 |

257 | 258 |

259 | Intl.MessageFormat instances also have several internal slots that are computed by the constructor: 260 |

261 | 262 |
    263 |
  • 264 | [[LocaleMatcher]] is one of the String values *"lookup"* or *"best fit"*, 265 | identifying the locale matcher used. 266 |
  • 267 |
  • 268 | [[MessageData]] is an Object conforming to the 269 | JSON Schema definition 270 | of the Unicode MessageFormat 2.0 specification. 271 |
  • 272 |
  • 273 | [[RequestedLocales]] is a List of String values 274 | with the canonicalized language tags of the requested locales 275 | to use for message formatting. 276 |
  • 277 |
  • [[Functions]] is an Object with function object values.
  • 278 |
279 |
280 | 281 | 282 |

Abstract Operations for MessageFormat Objects

283 | 284 | 285 |

286 | GetMessageData ( 287 | _source_: a String or an Object, 288 | ): an Object conforming to the JSON Schema definition of a message according to the Unicode MessageFormat 2.0 specification 289 |

290 |
291 |
description
292 |
293 |

294 | If _source_ is a String, it returns the message data representation corresponding to the input _source_ according to the 295 | Unicode MessageFormat 2.0 syntax. 296 | If _source_ is an Object, it checks that _source_ holds a valid message data representation, 297 | and returns an equivalent Object that is not affected by any further changes to _source_. 298 |

299 | 300 |

If _source_ contains a syntax or data model error, this operation throws a *SyntaxError*.

301 |
302 |
303 |
304 | 305 | 306 |

307 | HandleDateTimeInput ( 308 | _input_: an ECMAScript value, 309 | _opts_: an ECMAScript value, 310 | ): a Date object 311 |

312 |
313 |
description
314 |
It accepts an input that could be a Date object, a number or a string and converts them into Date objects.
315 |
316 | 317 | 318 | 1. If InstanceOfOperator(_input_, %Date%) is *true*, then 319 | 1. If HasProperty(_input_, *"options"*) is *true*, then 320 | 1. Perform ? Call(Object.assign, *undefined*, « _opts_, Get(_input_, *"options"*) »). 321 | 1. Return _input_. 322 | 1. If Type(_input_) is Object, then 323 | 1. Let _valueOf_ be ? Get(_input_, *"valueOf"*). 324 | 1. If IsCallable(_valueOf_) is *true*, let _value_ be Call(_valueOf_, _input_). 325 | 1. Else throw a *TypeError* exception. 326 | 1. If HasProperty(_input_, *"options"*) is *true*, then 327 | 1. Perform ? Call(Object.assign, *undefined*, « _opts_, Get(_input_, *"options"*) »). 328 | 1. Else, 329 | 1. Let _value_ be _input_. 330 | 1. If Type(_value_) is Number or String, then 331 | 1. Return ? Construct(%Date%, « _value_ »). 332 | 1. If InstanceOfOperator(_value_, %Date%) is *false*, throw a *TypeError* exception. 333 | 1. Return _value_. 334 | 335 |
336 | 337 | 338 |

339 | DateTimeMessageValue ( 340 | _value_: a Date object, 341 | _opts_: an Object, 342 | _funcCtx_: an Object, 343 | ): an ECMAScript value 344 |

345 | 346 | 1. Let _locale_ be ! Get(_funcCtx_, *"locale"*). 347 | 1. Let _source_ be ! Get(_funcCtx_, *"source"*). 348 | 1. Let _dateTimeFormat_ be ? Construct(%Intl.DateTimeFormat%, « _locale_, _opts_ »). 349 | 1. Let _toPartsClosure_ be a new Abstract Closure with no parameters that captures _value_, _source_, and _dateTimeFormat_ and performs the following steps when called: 350 | 1. Let _parts_ be ? Call(Intl.DateTimeFormat.prototype.formatToParts, _dateTimeFormat_, « _value_ »). 351 | 1. Let _result_ be OrdinaryObjectCreate(%Object.prototype%). 352 | 1. Perform ! CreateDataPropertyOrThrow(_result_, *"type"*, *"datetime"*). 353 | 1. Perform ! CreateDataPropertyOrThrow(_result_, *"source"*, _source_). 354 | 1. Perform ! CreateDataPropertyOrThrow(_result_, *"locale"*, _dateTimeFormat_.[[Locale]]). 355 | 1. Perform ! CreateDataPropertyOrThrow(_result_, *"parts"*, _parts_). 356 | 1. Return CreateArrayFromList(« _result_ »). 357 | 1. Let _toParts_ be CreateBuiltinFunction(_toPartsClosure_, *0*, *"toParts"*, « »). 358 | 1. Let _toStringClosure_ be a new Abstract Closure with no parameters that captures _value_ and _dateTimeFormat_ and performs the following steps when called: 359 | 1. Return ? Call(Intl.DateTimeFormat.prototype.format, _dateTimeFormat_, « _value_ »). 360 | 1. Let _toString_ be CreateBuiltinFunction(_toStringClosure_, *0*, *"toString"*, « »). 361 | 1. Let _valueOfClosure_ be a new Abstract Closure with no parameters that captures _value_ and performs the following steps when called: 362 | 1. Return _value_. 363 | 1. Let _valueOf_ be CreateBuiltinFunction(_valueOfClosure_, *0*, *"valueOf"*, « »). 364 | 1. Let _mv_ be OrdinaryObjectCreate(%Object.prototype%). 365 | 1. Perform ! CreateDataPropertyOrThrow(_mv_, *"type"*, *"datetime"*). 366 | 1. Perform ! CreateDataPropertyOrThrow(_mv_, *"source"*, _source_). 367 | 1. Perform ! CreateDataPropertyOrThrow(_mv_, *"locale"*, _dateTimeFormat_.[[Locale]]). 368 | 1. Perform ! CreateDataPropertyOrThrow(_mv_, *"options"*, _opts_). 369 | 1. Perform ! CreateDataPropertyOrThrow(_mv_, *"toParts"*, _toParts_). 370 | 1. Perform ! CreateDataPropertyOrThrow(_mv_, *"toString"*, _toString_). 371 | 1. Perform ! CreateDataPropertyOrThrow(_mv_, *"valueOf"*, _valueOf_). 372 | 1. Return _mv_. 373 | 374 |
375 | 376 | 377 |

378 | GetMessageFunctions ( 379 | _userFunctions_: an Object or undefined, 380 | ): an Object 381 |

382 |
383 |
description
384 |
It determines the functions available during message formatting.
385 |
386 | 387 | 388 | 1. Let _numberSteps_ be the algorithm steps defined in . 389 | 1. Let _number_ be CreateBuiltinFunction(_numberSteps_, *3*, *"number"*, « »). 390 | 1. Let _stringSteps_ be the algorithm steps defined in . 391 | 1. Let _string_ be CreateBuiltinFunction(_stringSteps_, *3*, *"string"*, « »). 392 | 1. Let _dateTimeSteps_ be the algorithm steps defined in . 393 | 1. Let _dateTime_ be CreateBuiltinFunction(_dateTimeSteps_, *3*, *"datetime"*, « »). 394 | 1. Let _dateSteps_ be the algorithm steps defined in . 395 | 1. Let _date_ be CreateBuiltinFunction(_dateSteps_, *3*, *"date"*, « »). 396 | 1. Let _timeSteps_ be the algorithm steps defined in . 397 | 1. Let _time_ be CreateBuiltinFunction(_timeSteps_, *3*, *"time"*, « »). 398 | 1. Let _functions_ be OrdinaryObjectCreate(%Object.prototype%). 399 | 1. Perform ! CreateDataPropertyOrThrow(_functions_, *"number"*, _number_). 400 | 1. Perform ! CreateDataPropertyOrThrow(_functions_, *"string"*, _string_). 401 | 1. Perform ! CreateDataPropertyOrThrow(_functions_, *"datetime"*, _dateTime_). 402 | 1. Perform ! CreateDataPropertyOrThrow(_functions_, *"date"*, _date_). 403 | 1. Perform ! CreateDataPropertyOrThrow(_functions_, *"time"*, _time_). 404 | 1. For each String _name_ of ? EnumerableOwnProperties(_userFunctions_, ~key~), 405 | 1. Let _func_ be ? Get(_userFunctions_, _name_). 406 | 1. If IsCallable(_func_) is *true*, then 407 | 1. Perform ! CreateDataPropertyOrThrow(_functions_, _name_, _func_). 408 | 1. Else, 409 | 1. Throw a *TypeError* exception. 410 | 1. Return _functions_. 411 | 412 | 413 | 414 |

MessageFormat Number Functions

415 |

416 | A MessageFormat number function is an anonymous built-in function. 417 |

418 |

419 | When a MessageFormat number function is called with arguments _funcCtx_ (an Object), _options_ (an Object), and _input_ (an ECMAScript language value), the following steps are taken: 420 |

421 | 422 | 423 | 1. Let _locale_ be ! Get(_funcCtx_, *"locale"*). 424 | 1. Let _localeMatcher_ be ! Get(_funcCtx_, *"localeMatcher"*). 425 | 1. Let _source_ be ! Get(_funcCtx_, *"source"*). 426 | 1. Let _opts_ be OrdinaryObjectCreate(%Object.prototype%). 427 | 1. Perform ! CreateDataPropertyOrThrow(_opt_, *"localeMatcher"*, _localeMatcher_). 428 | 1. If Type(_input_) is Object, then 429 | 1. Let _valueOf_ be ? Get(_input_, *"valueOf"*). 430 | 1. If IsCallable(_valueOf_) is *true*, then 431 | 1. Let _inputOptions_ be ? Get(_input_, *"options"*). 432 | 1. Perform ? Call(Object.assign, *undefined*, « _opts_, _inputOptions_ »). 433 | 1. Set _input_ to ? Call(_valueOf_, _input_). 434 | 1. If Type(_input_) is String, then 435 | 1. Set _input_ to ? Call(JSON.parse, %JSON%, « _input_ »). 436 | 1. If Type(_input_) is not Number or BigInt, throw a *TypeError* exception. 437 | 1. Let _numberOptions_ be « *"minimumIntegerDigits"*, *"minimumFractionDigits"*, *"maximumFractionDigits"*, *"minimumSignificantDigits"*, *"maximumSignificantDigits"*, *"roundingIncrement"* ». 438 | 1. Let _booleanOptions_ be « *"useGrouping"* ». 439 | 1. Let _select_ be *"cardinal"*. 440 | 1. For each String _optName_ of ? EnumerableOwnProperties(_options_, ~key~), 441 | 1. If _optName_ is not *"localeMatcher"* and _optName_ is not *"type"*, then 442 | 1. Let _optValue_ be ? Get(_options_, _optName_). 443 | 1. If Type(_optValue_) is Object, then 444 | 1. Let _optValueOf_ be ? Get(_optValue_, *"valueOf"*). 445 | 1. If IsCallable(_optValueOf_) is *true*, then 446 | 1. Set _optValue_ to ? Call(_optValueOf_, _optValue_). 447 | 1. If _optName_ is *"select"*, then 448 | 1. If _optValue_ is *"ordinal"* or _optValue_ is *"exact"*, then 449 | 1. Set _select_ to _optValue_ 450 | 1. Else if _optValue_ is not *"plural"*, then 451 | 1. Throw a *RangeError* exception. 452 | 1. Else, 453 | 1. If Type(_optValue_) is String, then 454 | 1. If _numberOptions_ contains _optName_, then 455 | 1. Set _optValue_ to ToNumber(_optValue_). 456 | 1. Else if _booleanOptions_ contains _optName_, then 457 | 1. If _optValue_ is *"true"*, then 458 | 1. Set _optValue_ to *true*. 459 | 1. Else if _optValue_ is *"false"*, then 460 | 1. Set _optValue_ to *false*. 461 | 1. Perform ? Set(_opts_, _optName_, _optValue_, *true*). 462 | 1. Let _numberFormat_ be ? Construct(%Intl.NumberFormat%, « _locale_, _opts_ »). 463 | 1. If _select_ is *"exact"*, then 464 | 1. Perform ? _opts_.[[Delete]](*"type"*). 465 | 1. Let _pluralRules_ be *undefined*. 466 | 1. Else, 467 | 1. Perform ? Set(_opts_, *"type"*, _select_, *true*). 468 | 1. Let _pluralRules_ be ? Construct(%Intl.PluralRules%, « _locale_, _opts_ »). 469 | 1. Let _selectKeyClosure_ be a new Abstract Closure with parameters (_keys_) that captures _input_ and _pluralRules_ and performs the following steps when called: 470 | 1. Let _keyList_ be ? CreateListFromArrayLike(_keys_, « String »). 471 | 1. Let _str_ be ? ToString(_input_). 472 | 1. If _keyList_ contains _str_, return _str_. 473 | 1. If _pluralRules_ is not *undefined*, then 474 | 1. Let _category_ be ? Call(Intl.PluralRules.prototype.select, _pluralRules_, « _input_ »). 475 | 1. If _keyList_ contains _category_, return _category_. 476 | 1. Return *null*. 477 | 1. Let _selectKey_ be CreateBuiltinFunction(_selectKeyClosure_, *1*, *"selectKey"*, « »). 478 | 1. Let _toPartsClosure_ be a new Abstract Closure with no parameters that captures _input_, _source_, and _numberFormat_ and performs the following steps when called: 479 | 1. Let _parts_ be ? Call(Intl.NumberFormat.prototype.formatToParts, _numberFormat_, « _input_ »). 480 | 1. Let _result_ be OrdinaryObjectCreate(%Object.prototype%). 481 | 1. Perform ! CreateDataPropertyOrThrow(_result_, *"type"*, *"number"*). 482 | 1. Perform ! CreateDataPropertyOrThrow(_result_, *"source"*, _source_). 483 | 1. Perform ! CreateDataPropertyOrThrow(_result_, *"locale"*, _numberFormat_.[[Locale]]). 484 | 1. Perform ! CreateDataPropertyOrThrow(_result_, *"parts"*, _parts_). 485 | 1. Return CreateArrayFromList(« _result_ »). 486 | 1. Let _toParts_ be CreateBuiltinFunction(_toPartsClosure_, *0*, *"toParts"*, « »). 487 | 1. Let _toStringClosure_ be a new Abstract Closure with no parameters that captures _input_ and _numberFormat_ and performs the following steps when called: 488 | 1. Return ? Call(Intl.NumberFormat.prototype.format, _numberFormat_, « _input_ »). 489 | 1. Let _toString_ be CreateBuiltinFunction(_toStringClosure_, *0*, *"toString"*, « »). 490 | 1. Let _valueOfClosure_ be a new Abstract Closure with no parameters that captures _input_ and performs the following steps when called: 491 | 1. Return _input_. 492 | 1. Let _valueOf_ be CreateBuiltinFunction(_valueOfClosure_, *0*, *"valueOf"*, « »). 493 | 1. Let _mv_ be OrdinaryObjectCreate(%Object.prototype%). 494 | 1. Perform ! CreateDataPropertyOrThrow(_mv_, *"type"*, *"number"*). 495 | 1. Perform ! CreateDataPropertyOrThrow(_mv_, *"source"*, _source_). 496 | 1. Perform ! CreateDataPropertyOrThrow(_mv_, *"locale"*, _numberFormat_.[[Locale]]). 497 | 1. Perform ! CreateDataPropertyOrThrow(_mv_, *"options"*, _opts_). 498 | 1. Perform ! CreateDataPropertyOrThrow(_mv_, *"selectKey"*, _selectKey_). 499 | 1. Perform ! CreateDataPropertyOrThrow(_mv_, *"toParts"*, _toParts_). 500 | 1. Perform ! CreateDataPropertyOrThrow(_mv_, *"toString"*, _toString_). 501 | 1. Perform ! CreateDataPropertyOrThrow(_mv_, *"valueOf"*, _valueOf_). 502 | 1. Return _mv_. 503 | 504 | 505 |

506 | The *"length"* property of a MessageFormat number function is *3*. 507 |

508 |
509 | 510 | 511 |

MessageFormat String Functions

512 |

513 | A MessageFormat string function is an anonymous built-in function. 514 |

515 |

516 | When a MessageFormat string function is called with arguments _funcCtx_ (an Object), _options_ (an Object), and _input_ (an ECMAScript language value), the following steps are taken: 517 |

518 | 519 | 520 | 1. Let _str_ be ? ToString(_input_). 521 | 1. Let _source_ be ! Get(_funcCtx_, *"source"*). 522 | 1. Let _locale_ be ! Get(_funcCtx_, *"locale"*). 523 | 1. Let _locale0_ be ! Get(_locale_, *"0"*). 524 | 1. Let _selectKeyClosure_ be a new Abstract Closure with parameters (_keys_) that captures _str_ and performs the following steps when called: 525 | 1. Let _keyList_ be ? CreateListFromArrayLike(_keys_, « String »). 526 | 1. If _keyList_ contains _str_, return _str_. 527 | 1. Else, return *null*. 528 | 1. Let _selectKey_ be CreateBuiltinFunction(_selectKeyClosure_, *1*, *"selectKey"*, « »). 529 | 1. Let _toPartsClosure_ be a new Abstract Closure with no parameters that captures _funcCtx_ and _str_ and performs the following steps when called: 530 | 1. Let _result_ be OrdinaryObjectCreate(%Object.prototype%). 531 | 1. Perform ! CreateDataPropertyOrThrow(_result_, *"type"*, *"string"*). 532 | 1. Perform ! CreateDataPropertyOrThrow(_result_, *"source"*, _source_). 533 | 1. Perform ! CreateDataPropertyOrThrow(_result_, *"locale"*, _locale0_). 534 | 1. Perform ! CreateDataPropertyOrThrow(_result_, *"value"*, _str_). 535 | 1. Return CreateArrayFromList(« _result_ »). 536 | 1. Let _toParts_ be CreateBuiltinFunction(_toPartsClosure_, *0*, *"toParts"*, « »). 537 | 1. Let _toStringClosure_ be a new Abstract Closure with no parameters that captures _str_ and performs the following steps when called: 538 | 1. Return _str_. 539 | 1. Let _toString_ be CreateBuiltinFunction(_toStringClosure_, *0*, *"toString"*, « »). 540 | 1. Let _valueOf_ be CreateBuiltinFunction(_toStringClosure_, *0*, *"valueOf"*, « »). 541 | 1. Let _mv_ be OrdinaryObjectCreate(%Object.prototype%). 542 | 1. Perform ! CreateDataPropertyOrThrow(_mv_, *"type"*, *"string"*). 543 | 1. Perform ! CreateDataPropertyOrThrow(_mv_, *"source"*, _source_). 544 | 1. Perform ! CreateDataPropertyOrThrow(_mv_, *"locale"*, _locale0_). 545 | 1. Perform ! CreateDataPropertyOrThrow(_mv_, *"selectKey"*, _selectKey_). 546 | 1. Perform ! CreateDataPropertyOrThrow(_mv_, *"toParts"*, _toParts_). 547 | 1. Perform ! CreateDataPropertyOrThrow(_mv_, *"toString"*, _toString_). 548 | 1. Perform ! CreateDataPropertyOrThrow(_mv_, *"valueOf"*, _valueOf_). 549 | 1. Return _mv_. 550 | 551 | 552 |

553 | The *"length"* property of a MessageFormat string function is *3*. 554 |

555 |
556 | 557 | 558 |

MessageFormat DateTime Functions

559 |

560 | A MessageFormat dateTime function is an anonymous built-in function. 561 |

562 |

563 | When a MessageFormat dateTime function is called with arguments _funcCtx_ (an Object), _options_ (an Object), and _input_ (an ECMAScript language value), the following steps are taken: 564 |

565 | 566 | 567 | 1. Let _opts_ be OrdinaryObjectCreate(%Object.prototype%). 568 | 1. Let _localeMatcher_ be ! Get(_funcCtx_, *"localeMatcher"*). 569 | 1. Perform ! CreateDataPropertyOrThrow(_opts_, *"localeMatcher"*, _localeMatcher_). 570 | 1. Let _value_ be ? HandleDateTimeInput(_input_, _opts_). 571 | 1. For each String _optName_ of ? EnumerableOwnProperties(_options_, ~key~), 572 | 1. If _optName_ is not *"locale"*, then 573 | 1. Let _optValue_ be ? Get(_options_, _optName_). 574 | 1. If Type(_optValue_) is Object, then 575 | 1. Let _optValueOf_ be ? Get(_optValue_, *"valueOf"*). 576 | 1. If IsCallable(_optValueOf_) is *true*, then 577 | 1. Set _optValue__ to ? Call(_optValueOf_, _optValue_). 578 | 1. If _optName_ is *"fractionalSecondDigits"*, set _optValue_ to ToNumber(_optValue_). 579 | 1. Else if _optName_ is *"hour12"*, then 580 | 1. If _optValue_ is *"true"*, then 581 | 1. Set _optValue_ to *true*. 582 | 1. Else if _optValue_ is *"false"*, then 583 | 1. Set _optValue_ to *false*. 584 | 1. Else, set _optValue_ to ToString(_optValue_). 585 | 1. Perform ? Set(_opts_, _optName_, _optValue_, *"true"*). 586 | 1. Return ? DateTimeMessageValue(_value_, _opts_, _funcCtx_). 587 | 588 | 589 |

590 | The *"length"* property of a MessageFormat dateTime function is *3*. 591 |

592 |
593 | 594 | 595 |

MessageFormat Date Functions

596 |

597 | A MessageFormat date function is an anonymous built-in function. 598 |

599 |

600 | When a MessageFormat date function is called with arguments _funcCtx_ (an Object), _options_ (an Object), and _input_ (an ECMAScript language value), the following steps are taken: 601 |

602 | 603 | 604 | 1. Let _opts_ be OrdinaryObjectCreate(%Object.prototype%). 605 | 1. Let _localeMatcher_ be ! Get(_funcCtx_, *"localeMatcher"*). 606 | 1. Perform ! CreateDataPropertyOrThrow(_opts_, *"localeMatcher"*, _localeMatcher_). 607 | 1. Let _value_ be ? HandleDateTimeInput(_input_, _opts_). 608 | 1. Let _dateStyle_ be ! Get(_options_, *"style"*). 609 | 1. If _dateStyle_ is *null* or *undefined*, then 610 | 1. Set _dateStyle_ to ! Get(_opts_, *"dateStyle"*). 611 | 1. If _dateStyle_ is *null* or *undefined*, then 612 | 1. Set _dateStyle_ to *"short"*. 613 | 1. For each String _optName_ of ? EnumerableOwnProperties(_opts_, ~key~), 614 | 1. If _optName_ is not *"calendar"*, *"localeMatcher"*, *"hour12"*, *"hourCycle"*, *"numberingSystem"*, or *"timeZone"*, then 615 | 1. Perform ? DeletePropertyOrThrow(_opts_, _optName_). 616 | 1. Perform ? Set(_opts_, *"dateStyle"*, ToString(_dateStyle_)). 617 | 1. Return ? DateTimeMessageValue(_value_, _opts_, _funcCtx_). 618 | 619 | 620 |

621 | The *"length"* property of a MessageFormat date function is *3*. 622 |

623 |
624 | 625 | 626 |

MessageFormat Time Functions

627 |

628 | A MessageFormat time function is an anonymous built-in function. 629 |

630 |

631 | When a MessageFormat time function is called with arguments _funcCtx_ (an Object), _options_ (an Object), and _input_ (an ECMAScript language value), the following steps are taken: 632 |

633 | 634 | 635 | 1. Let _opts_ be OrdinaryObjectCreate(%Object.prototype%). 636 | 1. Let _localeMatcher_ be ! Get(_funcCtx_, *"localeMatcher"*). 637 | 1. Perform ! CreateDataPropertyOrThrow(_opts_, *"localeMatcher"*, _localeMatcher_). 638 | 1. Let _value_ be ? HandleDateTimeInput(_input_, _opts_). 639 | 1. Let _timeStyle_ be ! Get(_options_, *"style"*). 640 | 1. If _timeStyle_ is *null* or *undefined*, then 641 | 1. Set _timeStyle_ to ! Get(_opts_, *"timeStyle"*). 642 | 1. If _timeStyle_ is *null* or *undefined*, then 643 | 1. Set _timeStyle_ to *"short"*. 644 | 1. For each String _optName_ of ? EnumerableOwnProperties(_opts_, ~key~), 645 | 1. If _optName_ is not *"calendar"*, *"localeMatcher"*, *"hour12"*, *"hourCycle"*, *"numberingSystem"*, or *"timeZone"*, then 646 | 1. Perform ? DeletePropertyOrThrow(_opts_, _optName_). 647 | 1. Perform ? Set(_opts_, *"timeStyle"*, ToString(_timeStyle_)). 648 | 1. Return ? DateTimeMessageValue(_value_, _opts_, _funcCtx_). 649 | 650 | 651 |

652 | The *"length"* property of a MessageFormat time function is *3*. 653 |

654 |
655 |
656 | 657 | 658 |

659 | CreateMessageFormatContext ( 660 | _mf_: an Object, 661 | _values_: an Object or undefined, 662 | _onError_: a Function or undefined 663 | ): a MessageFormatContext Record 664 |

665 |
666 |
description
667 |
It initializes the MessageFormatContext Record used for pattern selection and formatting.
668 |
669 | 670 | 671 | 1. Let _localVars_ be OrdinaryObjectCreate(*null*). 672 | 1. Let _msgData_ be _mf_.[[MessageData]]. 673 | 1. Let _declArray_ be ? Get(_msgData_, *"declarations"*). 674 | 1. Let _declList_ be ? CreateListFromArrayLike(_declArray_). 675 | 1. For each element _decl_ of _declList_, 676 | 1. Let _declName_ be ? Get(_decl_, *"name"*). 677 | 1. Let _declValue_ be OrdinaryObjectCreate(*null*, « [[UnresolvedDeclaration]] »). 678 | 1. Set _declValue_.[[UnresolvedDeclaration]] to _decl_. 679 | 1. Perform ? Set(_localVars_, _declName_, _declValue_, *true*). 680 | 1. Let _ctx_ be a new Record. 681 | 1. Set _ctx_.[[LocalVariables]] to _localVars_. 682 | 1. Set _ctx_.[[MessageFormat]] to _mf_. 683 | 1. Set _ctx_.[[OnError]] to _onError_. 684 | 1. Set _ctx_.[[Values]] to ? GetOptionsObject(_values_). 685 | 1. Return _ctx_. 686 | 687 | 688 | 689 |

MessageFormatContext Records

690 |

691 | A MessageFormatContext Record is used to hold the data required during the formatting of a message. 692 | It has the fields defined in . 693 |

694 | 695 | 696 | Record returned by CreateMessageContext 697 | 698 | 699 | 700 | 701 | 702 | 703 | 704 | 705 | 706 | 707 | 708 | 712 | 713 | 714 | 715 | 716 | 717 | 718 | 719 | 720 | 721 | 722 | 723 | 724 | 725 | 726 | 727 | 728 |
Field NameValue TypeDescription
[[LocalVariables]]Object 709 | Cache for local variables, which may be resolved during the formatting. 710 | The values in the cache are Objects, each of which will have either [[MessageValue]], [[ResolvedValue]], or [[UnresolvedDeclaration]] internal slots. 711 |
[[MessageFormat]]Intl.MessageFormatThe MessageFormat instance on which the formatting is taking place.
[[OnError]]Function object or *undefined*.The _onError_ argument of the formatter call.
[[Values]]ObjectThe _values_ argument of the formatter call.
729 |
730 |
731 |
732 | 733 | 734 |

735 | ResolveMessage ( 736 | _ctx_: a MessageFormatContext Record 737 | ): a List of Strings and Objects 738 |

739 |
740 |
description
741 |
It resolves a message into a List of Strings and Objects with either [[MessageMarkup]] or [[MessageValue]] internal slots.
742 |
743 | 744 | 745 | 1. Let _result_ be an empty List. 746 | 1. Let _pattern_ be ? SelectPattern(_ctx_). 747 | 1. For each element _el_ of _pattern_, 748 | 1. If Type(_el_) is String, then 749 | 1. Append _el_ to _result_. 750 | 1. Else, 751 | 1. Assert: Type(_el_) is Object. 752 | 1. Let _elType_ be ? Get(_el_, *"type"*). 753 | 1. If _elType_ is *"markup"*, then 754 | 1. Let _res_ be OrdinaryObjectCreate(*null*, « [[MessageMarkup]] »). 755 | 1. Set _res_.[[MessageMarkup]] to _el_. 756 | 1. Else, 757 | 1. Assert: _elType_ is *"expression"*. 758 | 1. Let _res_ be ResolveExpression(_ctx_, _el_). 759 | 1. Append _res_ to _result_. 760 | 1. Return _result_. 761 | 762 | 763 | 764 |

765 | SelectPattern ( 766 | _ctx_: a MessageFormatContext Record 767 | ): a List of Strings and Objects with an internal slot [[MessageValue]] 768 |

769 |
770 |
description
771 |
It selects the pattern to format from the message data model.
772 |
773 | 774 | 775 | 1. Let _msgData_ be _ctx_.[[MessageFormat]].[[MessageData]]. 776 | 1. Let _msgType_ be ? Get(_msgData_, *"type"*). 777 | 1. If _msgType_ is *"message"*, then 778 | 1. Let _pattern_ be ? Get(_msgData_, *"pattern"*). 779 | 1. Else, 780 | 1. Assert: _msgType_ is *"select"*. 781 | 1. Let _selectorsData_ be ? Get(_msgData_, *"selectors"*). 782 | 1. Let _selectorExpressions_ be ? CreateListFromArrayLike(_selectorsData_, « Object »). 783 | 1. Let _selectors_ be an empty List. 784 | 1. For each element _selExp_ of _selectorExpressions_, 785 | 1. Let _sel_ be ResolveExpression(_ctx_, _selExp_). 786 | 1. Let _selectKey_ be ? Get(_sel_.[[MessageValue]], *"selectKey"*). 787 | 1. If IsCallable(_selectKey_) is *true*, then 788 | 1. Append _sel_.[[MessageValue]] to _selectors_. 789 | 1. Else, 790 | 1. Let _error_ be ThrowCompletion(*TypeError*). 791 | 1. Perform ? HandleMessageFormatError(_ctx_, _error_). 792 | 1. Append *null* to _selectors_. 793 | 1. Let _variantsArray_ be ? Get(_msgData_, *"variants"*). 794 | 1. Let _variants_ be ? CreateListFromArrayLike(_variantsArray_, « Object »). 795 | 1. Let _selectedVariant_ be ? SelectVariant(_ctx_, _selectors_, _variants_). 796 | 1. Return ? CreateListFromArrayLike(_pattern_, « String, Object »). 797 | 798 |
799 | 800 | 801 |

802 | SelectVariant ( 803 | _ctx_: a MessageFormatContext Record, 804 | _selectors_: a List of Object and Null values, 805 | _variants_: a List of Objects 806 | ): an Object 807 |

808 |
809 |
description
810 |
811 |

812 | It applies the variant selection method defined by 813 | the "Resolve Preferences", "Filter Variants", and "Sort Variants" steps of the 814 | Pattern Selection 815 | section of the Unicode MessageFormat 2.0 specification. 816 |

817 | 818 |

819 | For the selection, Null values in _selectors_ indicate selectors for which selection will always fail. 820 | If any errors are encountered during the selection, 821 | the HandleMessageFormatError abstract operation must be called 822 | with _ctx_ and an abrupt Completion Record containing an Error. 823 |

824 | 825 |

826 | The value returned by this operation must be an Object matching the JSON Schema definition of a message pattern: 827 | 828 | https://github.com/unicode-org/message-format-wg/blob/main/spec/data-model/message.json#/$defs/pattern 829 | . 830 |

831 |
832 |
833 |
834 |
835 | 836 | 837 |

838 | ResolveExpression ( 839 | _ctx_: a MessageFormatContext Record, 840 | _expression_: an Object, 841 | ): an Object with an internal slot [[MessageValue]] 842 |

843 |
844 |
description
845 |
It resolves the value of an expression for selection and formatting.
846 |
847 | 848 | 849 | 1. Let _source_ be ? GetSource(_ctx_, _expression_). 850 | 1. Let _arg_ be ? Get(_expression_, *"arg"*). 851 | 1. If _arg_ is *undefined*, then 852 | 1. Let _resArg_ be *undefined*. 853 | 1. Else, 854 | 1. Let _resArg_ be ResolveValue(_ctx_, _arg_). 855 | 1. If _resArg_ has a [[MessageFallback]] internal slot, return _resArg_. 856 | 1. Let _annotation_ be ? Get(_expression_, *"annotation"*). 857 | 1. If _annotation_ is *undefined*, then 858 | 1. Assert: _resArg_ is not *undefined*. 859 | 1. If Type(_resArg_) is Object and _resArg_ has a [[MessageValue]] internal slot, then 860 | 1. Return _resArg_. 861 | 1. Else if Type(_resArg_) is Number or BigInt, then 862 | 1. Set _annotation_ to OrdinaryObjectCreate(*null*). 863 | 1. Perform ! CreateDataPropertyOrThrow(_annotation_, *"type"*, *"function"*). 864 | 1. Perform ! CreateDataPropertyOrThrow(_annotation_, *"name"*, *"number"*). 865 | 1. Else if Type(_resArg_) is String, then 866 | 1. Set _annotation_ to OrdinaryObjectCreate(*null*). 867 | 1. Perform ! CreateDataPropertyOrThrow(_annotation_, *"type"*, *"function"*). 868 | 1. Perform ! CreateDataPropertyOrThrow(_annotation_, *"name"*, *"string"*). 869 | 1. Else, 870 | 1. Return ResolveUnknown(_source_, _resArg_). 871 | 1. If ? Get(_annotation_, *"type"*) is not *"function"*, then 872 | 1. Let _error_ be ThrowCompletion(*ReferenceError*). 873 | 1. Perform ? HandleMessageFormatError(_ctx_, _error_). 874 | 1. Return ResolveFallback(_source_). 875 | 1. Return ResolveFunction(_ctx_, _source_, _resArg_, _annotation_). 876 | 877 | 878 | 879 |

880 | ResolveFunction ( 881 | _ctx_: a MessageFormatContext Record, 882 | _source_: a String, 883 | _resArg_: an Object or undefined, 884 | _annotation_: an Object, 885 | ): an Object with an internal slot [[MessageValue]] 886 |

887 |
888 |
description
889 |
It resolves the value of an expression with a function annotation.
890 |
891 | 892 | 893 | 1. Let _name_ be ? Get(_annotation_, *"name"*). 894 | 1. Let _function_ be ? Get(_ctx_.[[MessageFormat]].[[Functions]], _name_). 895 | 1. If IsCallable(_function_) is *false*, then 896 | 1. Let _error_ be ThrowCompletion(*ReferenceError*). 897 | 1. Perform ? HandleMessageFormatError(_ctx_, _error_). 898 | 1. Return ResolveFallback(_source_). 899 | 1. Let _funcCtx_ be OrdinaryObjectCreate(%Object.prototype%). 900 | 1. Let _locales_ be CreateArrayFromList(_ctx_.[[MessageFormat]].[[RequestedLocales]]). 901 | 1. Perform ! CreateDataPropertyOrThrow(_funcCtx_, *"locale"*, _locales_). 902 | 1. Perform ! CreateDataPropertyOrThrow(_funcCtx_, *"localeMatcher"*, _ctx_.[[MessageFormat]].[[LocaleMatcher]]). 903 | 1. Perform ! CreateDataPropertyOrThrow(_funcCtx_, *"source"*, _source_). 904 | 1. Let _options_ be OrdinaryObjectCreate(%Object.prototype%). 905 | 1. Let _optArray_ be ? Get(_annotation_, *"options"*). 906 | 1. If _optArray_ is not *undefined*, then 907 | 1. Let _optList_ be ? CreateListFromArrayLike(_optArray_). 908 | 1. For each element _opt_ of _optList_, 909 | 1. Let _optName_ be ? Get(_opt_, *"name"*). 910 | 1. Let _optValue_ be ? Get(_opt_, *"value"*). 911 | 1. Let _resValue_ be ResolveValue(_ctx_, _optValue_). 912 | 1. If Type(_resValue_) is Object, then 913 | 1. If _resValue_ has a [[MessageFallback]] internal slot, then 914 | 1. Set _resValue_ to *undefined*. 915 | 1. Else if _resValue_ has a [[MessageValue]] internal slot, then 916 | 1. Set _resValue_ to _resValue_.[[MessageValue]]. 917 | 1. Perform ? CreateDataPropertyOrThrow(_options_, _optName_, _resValue_). 918 | 1. Perform ! CreateDataPropertyOrThrow(_part_, *"options"*, _resOptions_). 919 | 1. Let _funcArgs_ be « _funcCtx_, _options_ ». 920 | 1. If Type(_resArg_) is Object and _resArg_ has a [[MessageValue]] internal slot, then 921 | 1. Set _resArg_ to _resArg_.[[MessageValue]]. 922 | 1. If _resArg_ is not *undefined*, then 923 | 1. Append _resArg_ to _funcArgs_. 924 | 1. Let _funcRes_ be Completion(Call(_function_, *undefined*, _funcArgs_)). 925 | 1. If _funcRes_ is a normal completion, then 926 | 1. Let _mv_ be _funcRes_.[[Value]]. 927 | 1. If Type(_mv_) is Object and Type(? Get(_mv_, *"type"*)) is String and Type(? Get(_mv_, *"source"*)) is String, then 928 | 1. Let _result_ be OrdinaryObjectCreate(*null*, « [[MessageValue]] »). 929 | 1. Set _result_.[[MessageValue]] to _mv_. 930 | 1. Return _result_. 931 | 1. Else, 932 | 1. Let _error_ be ThrowCompletion(*TypeError*). 933 | 1. Perform ? HandleMessageFormatError(_ctx_, _error_). 934 | 1. Return ResolveFallback(_source_). 935 | 1. Else, 936 | 1. Perform ? HandleMessageFormatError(_ctx_, _funcRes_). 937 | 1. Return ResolveFallback(_source_). 938 | 939 |
940 | 941 | 942 |

943 | ResolveUnknown ( 944 | _source_: a String, 945 | _value_: an ECMAScript language value 946 | ): an Object with an internal slot [[MessageValue]] 947 |

948 |
949 |
description
950 |
It resolves an unknown value.
951 |
952 | 953 | 954 | 1. Let _toPartsClosure_ be a new Abstract Closure with no parameters that captures _source_ and _value_ and performs the following steps when called: 955 | 1. Let _part_ be OrdinaryObjectCreate(%Object.prototype%). 956 | 1. Perform ! CreateDataPropertyOrThrow(_part_, *"type"*, *"unknown"*). 957 | 1. Perform ! CreateDataPropertyOrThrow(_part_, *"source"*, _source_). 958 | 1. Perform ! CreateDataPropertyOrThrow(_part_, *"value"*, _value_). 959 | 1. Return CreateArrayFromList(« _part_ »). 960 | 1. Let _toParts_ be CreateBuiltinFunction(_toPartsClosure_, *0*, *"toParts"*, « »). 961 | 1. Let _toStringClosure_ be a new Abstract Closure with no parameters that captures _value_ and performs the following steps when called: 962 | 1. Return ? ToString(_value_). 963 | 1. Let _toString_ be CreateBuiltinFunction(_toStringClosure_, *0*, *"toString"*, « »). 964 | 1. Let _valueOfClosure_ be a new Abstract Closure with no parameters that captures _value_ and performs the following steps when called: 965 | 1. Return _value_. 966 | 1. Let _valueOf_ be CreateBuiltinFunction(_valueOfClosure_, *0*, *"valueOf"*, « »). 967 | 1. Let _mv_ be OrdinaryObjectCreate(%Object.prototype%). 968 | 1. Perform ! CreateDataPropertyOrThrow(_mv_, *"type"*, *"unknown"*). 969 | 1. Perform ! CreateDataPropertyOrThrow(_mv_, *"source"*, _source_). 970 | 1. Perform ! CreateDataPropertyOrThrow(_mv_, *"locale"*, *"und"*). 971 | 1. Perform ! CreateDataPropertyOrThrow(_mv_, *"toParts"*, _toParts_). 972 | 1. Perform ! CreateDataPropertyOrThrow(_mv_, *"toString"*, _toString_). 973 | 1. Perform ! CreateDataPropertyOrThrow(_mv_, *"valueOf"*, _valueOf_). 974 | 1. Let _unknown_ be OrdinaryObjectCreate(*null*, « [[MessageValue]] »). 975 | 1. Set _unknown_.[[MessageValue]] to _mv_. 976 | 1. Return _unknown_. 977 | 978 |
979 |
980 | 981 | 982 |

983 | FormatMarkupPart ( 984 | _ctx_: a MessageFormatContext Record, 985 | _markup_: an Object, 986 | ): an Object 987 |

988 |
989 |
description
990 |
It resolves the formatted part value of a markup placeholder.
991 |
992 | 993 | 994 | 1. Let _kind_ be ? Get(_markup_, *"kind"*). 995 | 1. Let _name_ be ? Get(_markup_, *"name"*). 996 | 1. Let _options_ be ? Get(_markup_, *"options"*). 997 | 1. Let _part_ be OrdinaryObjectCreate(%Object.prototype%). 998 | 1. Perform ! CreateDataPropertyOrThrow(_part_, *"type"*, *"markup"*). 999 | 1. Perform ! CreateDataPropertyOrThrow(_part_, *"kind"*, _kind_). 1000 | 1. Perform ! CreateDataPropertyOrThrow(_part_, *"name"*, _name_). 1001 | 1. If _kind_ is not *"close"* and _options_ is not *undefined*, then 1002 | 1. Let _len_ be ? LengthOfArrayLike(_options_). 1003 | 1. If _len_ > 0, then 1004 | 1. Let _opts_ be ? CreateListFromArrayLike(_options_). 1005 | 1. Let _resOptions_ be OrdinaryObjectCreate(%Object.prototype%). 1006 | 1. For each element _opt_ of _opts_, 1007 | 1. Let _optName_ be ? Get(_opt_, *"name"*). 1008 | 1. Let _optValue_ be ? Get(_opt_, *"value"*). 1009 | 1. Let _resValue_ be ResolveValue(_ctx_, _optValue_). 1010 | 1. If Type(_resValue_) is Object, then 1011 | 1. If _resValue_ has a [[MessageFallback]] internal slot, then 1012 | 1. Set _resValue_ to *undefined*. 1013 | 1. Else, 1014 | 1. Let _valueOf_ be ? Get(_resValue_, *"valueOf"*). 1015 | 1. If IsCallable(_valueOf_) is *true*, then 1016 | 1. Let _valueOfResult_ be Completion(Call(_valueOf_, _resValue_)). 1017 | 1. If _valueOfResult_ is a normal completion, then 1018 | 1. Set _resValue_ to _valueOfResult_.[[Value]]. 1019 | 1. Else, 1020 | 1. Perform ? HandleMessageFormatError(_ctx_, _valueOfResult_). 1021 | 1. Set _resValue_ to *undefined*. 1022 | 1. Perform ! CreateDataPropertyOrThrow(_resOptions_, _optName_, _resValue_). 1023 | 1. Perform ! CreateDataPropertyOrThrow(_part_, *"options"*, _resOptions_). 1024 | 1. Return CreateArrayFromList(« _part_ »). 1025 | 1026 |
1027 | 1028 | 1029 |

1030 | GetSource ( 1031 | _ctx_: a MessageFormatContext Record, 1032 | _value_: an Object 1033 | ): a String 1034 |

1035 |
1036 |
description
1037 |
Determines the "source" value for a data model Object.
1038 |
1039 | 1040 | 1041 | 1. Let _type_ be ? Get(_value_, *"type"*). 1042 | 1. If _type_ is *"literal"*, then 1043 | 1. Let _strValue_ be ? Get(_value_, *"value"*). 1044 | 1. Modify _strValue_ such that all *"\"* characters are replaced with *"\\"*. 1045 | 1. Modify _strValue_ such that all *"|"* characters are replaced with *"\|"*. 1046 | 1. Return the string-concatenation of *"|"*, _strValue_, and *"|"*. 1047 | 1. If _type_ is *"variable"*, then 1048 | 1. Let _name_ be ? Get(_value_, *"name"*). 1049 | 1. Let _local_ be ! Get(_ctx_.[[LocalVariables]], _name_). 1050 | 1. If Type(_local_) is Object, then 1051 | 1. If _local_ has an [[UnresolvedDeclaration]] internal slot, then 1052 | 1. Let _decl_ be _local_.[[UnresolvedDeclaration]]. 1053 | 1. If ? Get(_decl_, *"type"*) is *"local"*, then 1054 | 1. Let _declValue_ be ? Get(_decl_, *"value"*). 1055 | 1. Return ? GetSource(_ctx_, _declValue_). 1056 | 1. Else if _local_ has a [[MessageValue]] internal slot, then 1057 | 1. Return MessageValueSource(_local_.[[MessageValue]]). 1058 | 1. Else, 1059 | 1. Assert: _local_ has a [[ResolvedName]] internal slot. 1060 | 1. Assert: _local_ has a [[ResolvedValue]] internal slot. 1061 | 1. Set _name_ to _local_.[[ResolvedName]]. 1062 | 1. Return the string-concatenation of *"$"* and _name_. 1063 | 1. If _type_ is *"markup"*, then 1064 | 1. Let _kind_ be ? Get(_value_, *"kind"*). 1065 | 1. Let _name_ be ? Get(_value_, *"name"*). 1066 | 1. If _kind_ is *"open"*, return the string-concatenation of *"#"* and _name_. 1067 | 1. If _kind_ is *"standalone"*, return the string-concatenation of *"#"*, _name_, and *"/"*. 1068 | 1. If _kind_ is *"close"*, return the string-concatenation of *"/"* and _name_. 1069 | 1. If _type_ is *"function"*, then 1070 | 1. Let _name_ be ? Get(_value_, *"name"*). 1071 | 1. Return the string-concatenation of *":"* and _name_. 1072 | 1. If _type_ is *"unsupported-annotation"*, then 1073 | 1. Return ? Get(_value_, *"sigil"*). 1074 | 1. If _type_ is *"expression"*, then 1075 | 1. Let _arg_ be ? Get(_value_, *"arg"*). 1076 | 1. If _arg_ is not *undefined*, return ? GetSource(_ctx_, _arg_). 1077 | 1. Let _annotation_ be ? Get(_value_, *"annotation"*). 1078 | 1. If _annotation_ is not *undefined*, return ? GetSource(_ctx_, _annotation_). 1079 | 1. Return *"\uFFFD"*. 1080 | 1081 | 1082 | 1083 |

1084 | MessageValueSource ( 1085 | _mv_: an Object 1086 | ): a String 1087 |

1088 |
1089 |
description
1090 |
1091 | It gets the source string representation of a resolved message value, 1092 | which should be always available. 1093 | On any error, it returns the Unicode replacement character � instead. 1094 |
1095 |
1096 | 1097 | 1098 | 1. Let _source_ be Completion(Get(_mv_, *"source"*)). 1099 | 1. If _source_ is a normal completion, then 1100 | 1. Let _str_ be _source_.[[Value]]. 1101 | 1. If Type(_str_) is String, then 1102 | 1. Return _str_. 1103 | 1. Return *"\uFFFD"*. 1104 | 1105 |
1106 |
1107 | 1108 | 1109 |

1110 | HandleMessageFormatError( 1111 | _ctx_: a MessageFormatContext Record, 1112 | _completionRecord_: a Completion Record, 1113 | ) 1114 |

1115 |
1116 |
description
1117 |
It handles errors during formatting.
1118 |
1119 | 1120 | 1121 | 1. Assert: _completionRecord_ is a Completion Record. 1122 | 1. Assert: _completionRecord_ is an abrupt completion. 1123 | 1. Let _onError_ be _ctx_.[[OnError]]. 1124 | 1. Let _error_ be _completionRecord_.[[Value]]. 1125 | 1. If _onError_ is not *undefined*, then 1126 | 1. Perform ? Call(_onError_, *undefined*, « _error_ »). 1127 | 1. Else if an appropriate notification mechanism exists, then 1128 | 1. Issue a warning for _error_. 1129 | 1130 |
1131 | 1132 | 1133 |

1134 | ResolveFallback ( 1135 | _source_: a String 1136 | ): an Object with internal slots [[MessageFallback]] and [[MessageValue]] 1137 |

1138 |
1139 |
description
1140 |
It resolves a fallback value.
1141 |
1142 | 1143 | 1144 | 1. Let _toPartsClosure_ be a new Abstract Closure with no parameters that captures _source_ and performs the following steps when called: 1145 | 1. Let _part_ be MessageFallbackPart(_source_). 1146 | 1. Return CreateArrayFromList(« _part_ »). 1147 | 1. Let _toParts_ be CreateBuiltinFunction(_toPartsClosure_, *0*, *"toParts"*, « »). 1148 | 1. Let _toStringClosure_ be a new Abstract Closure with no parameters that captures _source_ and performs the following steps when called: 1149 | 1. Return MessageFallbackString(_source_). 1150 | 1. Let _toString_ be CreateBuiltinFunction(_toStringClosure_, *0*, *"toString"*, « »). 1151 | 1. Let _mv_ be OrdinaryObjectCreate(%Object.prototype%). 1152 | 1. Perform ! CreateDataPropertyOrThrow(_mv_, *"source"*, _source_). 1153 | 1. Perform ! CreateDataPropertyOrThrow(_mv_, *"toParts"*, _toParts_). 1154 | 1. Perform ! CreateDataPropertyOrThrow(_mv_, *"toString"*, _toString_). 1155 | 1. Let _fallback_ be OrdinaryObjectCreate(*null*, « [[MessageFallback]], [[MessageValue]] »). 1156 | 1. Set _fallback_.[[MessageValue]] to _mv_. 1157 | 1. Return _fallback_. 1158 | 1159 | 1160 | 1161 |

1162 | MessageFallbackPart ( 1163 | _source_: a String 1164 | ): an Object 1165 |

1166 |
1167 |
description
1168 |
It gets the fallback formatted part representation.
1169 |
1170 | 1171 | 1172 | 1. Let _result_ be OrdinaryObjectCreate(%Object.prototype%). 1173 | 1. Perform ! CreateDataPropertyOrThrow(_result_, *"type"*, *"fallback"*). 1174 | 1. Perform ! CreateDataPropertyOrThrow(_result_, *"source"*, _source_). 1175 | 1. Return _result_. 1176 | 1177 |
1178 | 1179 | 1180 |

1181 | MessageFallbackString ( 1182 | _source_: a String 1183 | ): a String 1184 |

1185 |
1186 |
description
1187 |
It gets the fallback formatted string representation.
1188 |
1189 | 1190 | 1191 | 1. Return the string-concatenation of *"{"*, _source_, and *"}"*. 1192 | 1193 |
1194 |
1195 | 1196 | 1197 |

1198 | ResolveValue ( 1199 | _ctx_: a MessageFormatContext Record, 1200 | _value_: an Object, 1201 | ): an ECMAScript language value 1202 |

1203 |
1204 |
description
1205 |
1206 | It resolves the value of a literal or variable reference. 1207 | For literals, the returned value is always a String. 1208 | For variable references, the returned value may be any ECMAScript language value. 1209 | For unresolved variable references, the returned value is an Object with an internal slot [[MessageFallback]]. 1210 |
1211 |
1212 | 1213 | 1214 | 1. Let _type_ be ? Get(_value_, *"type"*). 1215 | 1. If _type_ is *"literal"*, then 1216 | 1. Return ? Get(_value_, *"value"*). 1217 | 1. Assert: _type_ is *"variable"*. 1218 | 1. Let _name_ be ? Get(_value_, *"name"*). 1219 | 1. Let _localVars_ be _ctx_.[[LocalVariables]]. 1220 | 1. Let _var_ be ! Get(_localVars_, _name_). 1221 | 1. If _var_ is *undefined*, then 1222 | 1. Set _var_ to ? Get(_ctx_.[[Values]], _name_). 1223 | 1. If _var_ is *undefined*, then 1224 | 1. Let _error_ be ThrowCompletion(*ReferenceError*). 1225 | 1. Perform ? HandleMessageFormatError(_ctx_, _error_). 1226 | 1. Let _source_ be ? GetSource(_value_). 1227 | 1. Set _var_ to ResolveFallback(_source_). 1228 | 1. Perform ! Set(_localVars_, _name_, _var_). 1229 | 1. Else, 1230 | 1. Let _resolved_ be OrdinaryObjectCreate(*null*, « [[ResolvedName]], [[ResolvedValue]] »). 1231 | 1. Set _resolved_.[[ResolvedName]] to _name_. 1232 | 1. Set _resolved_.[[ResolvedValue]] to _var_. 1233 | 1. Perform ! Set(_localVars_, _name_, _resolved_). 1234 | 1. Else, 1235 | 1. Assert: Type(_var_) is Object. 1236 | 1. If _var_ has a [[ResolvedValue]] internal slot, then 1237 | 1. Set _var_ to _var_.[[ResolvedValue]]. 1238 | 1. Else if _var_ has an [[UnresolvedDeclaration]] internal slot, then 1239 | 1. Let _decl_ be _var_.[[UnresolvedDeclaration]]. 1240 | 1. Let _declType_ be ? Get(_decl_, *"type"*). 1241 | 1. If _declType_ is *"input"*, then 1242 | 1. Let _expression_ be ? Get(_decl_, *"value"*). 1243 | 1. Perform ! Set(_localVars_, _name_, *undefined*). 1244 | 1. Set _var_ to ? ResolveExpression(_ctx_, _expression_). 1245 | 1. Else if _declType_ is *"local"*, then 1246 | 1. Let _expression_ be ? Get(_decl_, *"value"*). 1247 | 1. Set _var_ to ? ResolveExpression(_ctx_, _expression_). 1248 | 1. Else, 1249 | 1. Let _error_ be ThrowCompletion(*ReferenceError*). 1250 | 1. Perform ? HandleMessageFormatError(_ctx_, _error_). 1251 | 1. Let _source_ be ? GetSource(_value_). 1252 | 1. Set _var_ to ResolveFallback(_source_). 1253 | 1. Perform ! Set(_localVars_, _name_, _var_). 1254 | 1. Return _var_. 1255 | 1256 |
1257 |
1258 |
1259 | --------------------------------------------------------------------------------