@@ -37,6 +37,9 @@ export interface MessageNumberPart extends MessageExpressionPart {
3737 parts : Intl . NumberFormatPart [ ] ;
3838}
3939
40+ export type MessageNumberOptions = Intl . NumberFormatOptions &
41+ Intl . PluralRulesOptions & { select ?: 'exact' | 'cardinal' | 'ordinal' } ;
42+
4043const INT = Symbol ( 'INT' ) ;
4144
4245export function readNumericOperand (
@@ -65,6 +68,57 @@ export function readNumericOperand(
6568 return { value, options } ;
6669}
6770
71+ export function getMessageNumber (
72+ { dir, locales, source } : MessageFunctionContext ,
73+ value : number | bigint ,
74+ options : MessageNumberOptions
75+ ) : MessageNumber {
76+ let locale : string | undefined ;
77+ let nf : Intl . NumberFormat | undefined ;
78+ let cat : Intl . LDMLPluralRule | undefined ;
79+ let str : string | undefined ;
80+ return {
81+ type : 'number' ,
82+ source,
83+ get dir ( ) {
84+ if ( dir == null ) {
85+ locale ??= Intl . NumberFormat . supportedLocalesOf ( locales , options ) [ 0 ] ;
86+ dir = getLocaleDir ( locale ) ;
87+ }
88+ return dir ;
89+ } ,
90+ get options ( ) {
91+ return { ...options } ;
92+ } ,
93+ selectKey ( keys ) {
94+ const str = String ( value ) ;
95+ if ( keys . has ( str ) ) return str ;
96+ if ( options . select === 'exact' ) return null ;
97+ const pluralOpt = options . select
98+ ? { ...options , select : undefined , type : options . select }
99+ : options ;
100+ // Intl.PluralRules needs a number, not bigint
101+ cat ??= new Intl . PluralRules ( locales , pluralOpt ) . select ( Number ( value ) ) ;
102+ return keys . has ( cat ) ? cat : null ;
103+ } ,
104+ toParts ( ) {
105+ nf ??= new Intl . NumberFormat ( locales , options ) ;
106+ const parts = nf . formatToParts ( value ) ;
107+ locale ??= nf . resolvedOptions ( ) . locale ;
108+ dir ??= getLocaleDir ( locale ) ;
109+ return dir === 'ltr' || dir === 'rtl'
110+ ? [ { type : 'number' , source, dir, locale, parts } ]
111+ : [ { type : 'number' , source, locale, parts } ] ;
112+ } ,
113+ toString ( ) {
114+ nf ??= new Intl . NumberFormat ( locales , options ) ;
115+ str ??= nf . format ( value ) ;
116+ return str ;
117+ } ,
118+ valueOf : ( ) => value
119+ } ;
120+ }
121+
68122/**
69123 * `number` accepts a number, BigInt or string representing a JSON number as input
70124 * and formats it with the same options as
@@ -79,23 +133,17 @@ export function number(
79133 exprOpt : Record < string | symbol , unknown > ,
80134 operand ?: unknown
81135) : MessageNumber {
82- const { locales, source } = ctx ;
83- const options : Intl . NumberFormatOptions &
84- Intl . PluralRulesOptions & { select ?: 'exact' | 'cardinal' | 'ordinal' } = {
85- localeMatcher : ctx . localeMatcher
86- } ;
87- const input = readNumericOperand ( operand , source ) ;
136+ const input = readNumericOperand ( operand , ctx . source ) ;
88137 const value = input . value ;
89- Object . assign ( options , input . options ) ;
138+ const options : MessageNumberOptions = Object . assign (
139+ { localeMatcher : ctx . localeMatcher } ,
140+ input . options
141+ ) ;
90142
91143 for ( const [ name , optval ] of Object . entries ( exprOpt ) ) {
92144 if ( optval === undefined ) continue ;
93145 try {
94146 switch ( name ) {
95- case 'locale' :
96- case 'style' : // https://github.com/unicode-org/message-format-wg/pull/988
97- case 'type' : // used internally by Intl.PluralRules, but called 'select' here
98- break ;
99147 case 'minimumIntegerDigits' :
100148 case 'minimumFractionDigits' :
101149 case 'maximumFractionDigits' :
@@ -111,13 +159,17 @@ export function number(
111159 options [ name ] = strval === 'never' ? false : strval ;
112160 break ;
113161 }
114- default :
115- // @ts -expect-error Unknown options will be ignored
162+ case 'roundingMode' :
163+ case 'roundingPriority' :
164+ case 'select' : // Called 'type' in Intl.PluralRules
165+ case 'signDisplay' :
166+ case 'trailingZeroDisplay' :
167+ // @ts -expect-error Let Intl.NumberFormat construction fail
116168 options [ name ] = asString ( optval ) ;
117169 }
118170 } catch {
119171 const msg = `Value ${ optval } is not valid for :number option ${ name } ` ;
120- ctx . onError ( new MessageResolutionError ( 'bad-option' , msg , source ) ) ;
172+ ctx . onError ( new MessageResolutionError ( 'bad-option' , msg , ctx . source ) ) ;
121173 }
122174 }
123175
@@ -126,51 +178,7 @@ export function number(
126178 ? Math . round ( value as number )
127179 : value ;
128180
129- let locale : string | undefined ;
130- let dir = ctx . dir ;
131- let nf : Intl . NumberFormat | undefined ;
132- let cat : Intl . LDMLPluralRule | undefined ;
133- let str : string | undefined ;
134- return {
135- type : 'number' ,
136- source,
137- get dir ( ) {
138- if ( dir == null ) {
139- locale ??= Intl . NumberFormat . supportedLocalesOf ( locales , options ) [ 0 ] ;
140- dir = getLocaleDir ( locale ) ;
141- }
142- return dir ;
143- } ,
144- get options ( ) {
145- return { ...options } ;
146- } ,
147- selectKey ( keys ) {
148- const str = String ( num ) ;
149- if ( keys . has ( str ) ) return str ;
150- if ( options . select === 'exact' ) return null ;
151- const pluralOpt = options . select
152- ? { ...options , select : undefined , type : options . select }
153- : options ;
154- // Intl.PluralRules needs a number, not bigint
155- cat ??= new Intl . PluralRules ( locales , pluralOpt ) . select ( Number ( num ) ) ;
156- return keys . has ( cat ) ? cat : null ;
157- } ,
158- toParts ( ) {
159- nf ??= new Intl . NumberFormat ( locales , options ) ;
160- const parts = nf . formatToParts ( num ) ;
161- locale ??= nf . resolvedOptions ( ) . locale ;
162- dir ??= getLocaleDir ( locale ) ;
163- return dir === 'ltr' || dir === 'rtl'
164- ? [ { type : 'number' , source, dir, locale, parts } ]
165- : [ { type : 'number' , source, locale, parts } ] ;
166- } ,
167- toString ( ) {
168- nf ??= new Intl . NumberFormat ( locales , options ) ;
169- str ??= nf . format ( num ) ;
170- return str ;
171- } ,
172- valueOf : ( ) => num
173- } ;
181+ return getMessageNumber ( ctx , num , options ) ;
174182}
175183
176184/**
0 commit comments