@@ -34,15 +34,6 @@ function canonicalizeLocaleList(
3434 return Object . keys ( res )
3535}
3636
37- function getType ( opt : unknown ) {
38- const type =
39- // @ts -expect-error
40- opt && Object . prototype . hasOwnProperty . call ( opt , 'type' ) && opt . type
41- if ( ! type ) return 'cardinal'
42- if ( type === 'cardinal' || type === 'ordinal' ) return type
43- throw new RangeError ( 'Not a valid plural type: ' + JSON . stringify ( type ) )
44- }
45-
4637function toNumber ( value : unknown ) {
4738 switch ( typeof value ) {
4839 case 'number' :
@@ -59,18 +50,8 @@ export type Selector = (n: number | string, ord?: boolean) => Category
5950export type RangeSelector = ( start : Category , end : Category ) => Category
6051export type PluralRuleType = 'cardinal' | 'ordinal'
6152
62- export interface PluralRulesOptions {
63- localeMatcher ?: 'lookup' | 'best fit' | undefined
64- type ?: PluralRuleType | undefined
65- minimumIntegerDigits ?: number | undefined
66- minimumFractionDigits ?: number | undefined
67- maximumFractionDigits ?: number | undefined
68- minimumSignificantDigits ?: number | undefined
69- maximumSignificantDigits ?: number | undefined
70- roundingPriority ?: 'auto' | 'morePrecision' | 'lessPrecision'
71- }
72-
7353export interface ResolvedPluralRulesOptions {
54+ compactDisplay ?: 'short' | 'long'
7455 locale : string
7556 pluralCategories : Category [ ]
7657 type : PluralRuleType
@@ -79,7 +60,117 @@ export interface ResolvedPluralRulesOptions {
7960 maximumFractionDigits ?: number
8061 minimumSignificantDigits ?: number
8162 maximumSignificantDigits ?: number
63+ notation : 'standard' | 'scientific' | 'engineering' | 'compact'
64+ roundingIncrement :
65+ | 1
66+ | 2
67+ | 5
68+ | 10
69+ | 20
70+ | 25
71+ | 50
72+ | 100
73+ | 200
74+ | 250
75+ | 500
76+ | 1000
77+ | 2000
78+ | 2500
79+ | 5000
80+ roundingMode :
81+ | 'ceil'
82+ | 'floor'
83+ | 'expand'
84+ | 'trunc'
85+ | 'halfCeil'
86+ | 'halfFloor'
87+ | 'halfExpand'
88+ | 'halfTrunc'
89+ | 'halfEven'
8290 roundingPriority : 'auto' | 'morePrecision' | 'lessPrecision'
91+ trailingZeroDisplay : 'auto' | 'stripIfInteger'
92+ }
93+
94+ export type PluralRulesOptions = Partial <
95+ Omit < ResolvedPluralRulesOptions , 'pluralCategories' >
96+ >
97+
98+ function readOptions ( opt : PluralRulesOptions | undefined ) {
99+ if ( ! opt )
100+ return {
101+ type : 'cardinal' ,
102+ compactDisplay : 'short' ,
103+ nfOpt : undefined
104+ } as const
105+
106+ const get = < T extends string > ( name : string , values : T [ ] ) : T => {
107+ const val = Object . prototype . hasOwnProperty . call ( opt , name )
108+ ? ( opt as Record < string , unknown > ) [ name ]
109+ : undefined
110+ if ( val === undefined ) return values [ 0 ]
111+ if ( typeof val === 'symbol' )
112+ throw new TypeError ( `Unsupported symbol as ${ name } option value` )
113+ const strval = String ( val ) as T
114+ if ( ! values || values . includes ( strval ) ) return strval
115+ throw new RangeError ( `Unsupported ${ name } option value: ${ strval } ` )
116+ }
117+
118+ const localeMatcher = get ( 'localeMatcher' , [ 'best fit' , 'lookup' ] )
119+ const type = get ( 'type' , [ 'cardinal' , 'ordinal' ] )
120+ const notation = get ( 'notation' , [
121+ 'standard' ,
122+ 'scientific' ,
123+ 'engineering' ,
124+ 'compact'
125+ ] )
126+ const compactDisplay = get ( 'compactDisplay' , [ 'short' , 'long' ] )
127+ const {
128+ minimumIntegerDigits,
129+ minimumFractionDigits,
130+ maximumFractionDigits,
131+ minimumSignificantDigits,
132+ maximumSignificantDigits,
133+ roundingIncrement
134+ } = opt
135+ const roundingMode = get ( 'roundingMode' , [
136+ 'halfExpand' ,
137+ 'ceil' ,
138+ 'floor' ,
139+ 'expand' ,
140+ 'trunc' ,
141+ 'halfCeil' ,
142+ 'halfFloor' ,
143+ 'halfTrunc' ,
144+ 'halfEven'
145+ ] )
146+ const roundingPriority = get ( 'roundingPriority' , [
147+ 'auto' ,
148+ 'morePrecision' ,
149+ 'lessPrecision'
150+ ] )
151+ const trailingZeroDisplay = get ( 'trailingZeroDisplay' , [
152+ 'auto' ,
153+ 'stripIfInteger'
154+ ] )
155+
156+ return {
157+ type,
158+ compactDisplay,
159+ nfOpt : {
160+ localeMatcher,
161+ notation,
162+ compactDisplay : 'short' ,
163+ minimumIntegerDigits,
164+ minimumFractionDigits,
165+ maximumFractionDigits,
166+ minimumSignificantDigits,
167+ maximumSignificantDigits,
168+ roundingIncrement,
169+ roundingMode,
170+ roundingPriority,
171+ trailingZeroDisplay
172+ }
173+ } as const
83174}
84175
85176export interface PluralRules {
@@ -134,6 +225,7 @@ export default function getPluralRules(
134225 #range: RangeSelector
135226 #select: Selector
136227 #type: 'cardinal' | 'ordinal'
228+ #compactDisplay: 'short' | 'long'
137229 #nf: Intl . NumberFormat
138230
139231 constructor (
@@ -145,8 +237,10 @@ export default function getPluralRules(
145237 if ( ! this . #select)
146238 throw new Error ( `Selector not found for locale: ${ this . #locale} ` )
147239 this . #range = getRangeSelector ( this . #locale)
148- this . #type = getType ( opt )
149- this . #nf = new NumberFormat ( 'en' , opt ) // make-plural expects latin digits with . decimal separator
240+ const res = readOptions ( opt )
241+ this . #nf = new NumberFormat ( 'en' , res . nfOpt ) // make-plural expects latin digits with . decimal separator
242+ this . #type = res . type
243+ this . #compactDisplay = res . compactDisplay
150244 }
151245
152246 resolvedOptions ( ) : ResolvedPluralRulesOptions {
@@ -156,18 +250,27 @@ export default function getPluralRules(
156250 maximumFractionDigits,
157251 minimumSignificantDigits,
158252 maximumSignificantDigits,
159- roundingPriority
253+ notation,
254+ roundingIncrement,
255+ roundingMode,
256+ roundingPriority,
257+ trailingZeroDisplay
160258 } = this . #nf. resolvedOptions ( )
161259 const locale = this . #locale
162260 const type = this . #type
163261 return Object . assign (
164- { locale, type, minimumIntegerDigits } ,
262+ { locale, type, notation } ,
263+ notation === 'compact' && { compactDisplay : this . #compactDisplay } ,
264+ { minimumIntegerDigits } ,
165265 typeof minimumSignificantDigits === 'number'
166266 ? { minimumSignificantDigits, maximumSignificantDigits }
167267 : { minimumFractionDigits, maximumFractionDigits } ,
168268 {
169269 pluralCategories : getCategories ( locale , type === 'ordinal' ) . slice ( 0 ) ,
170- roundingPriority : roundingPriority ?? 'auto'
270+ roundingIncrement : roundingIncrement ?? 1 ,
271+ roundingMode : roundingMode ?? 'halfExpand' ,
272+ roundingPriority : roundingPriority ?? 'auto' ,
273+ trailingZeroDisplay : trailingZeroDisplay ?? 'auto'
171274 }
172275 )
173276 }
@@ -177,7 +280,10 @@ export default function getPluralRules(
177280 throw new TypeError ( `select() called on incompatible ${ this } ` )
178281 if ( typeof number !== 'number' ) number = Number ( number )
179282 if ( ! isFinite ( number ) ) return 'other'
180- const fmt = this . #nf. format ( Math . abs ( number ) )
283+ let fmt = ''
284+ for ( const part of this . #nf. formatToParts ( Math . abs ( number ) ) ) {
285+ fmt += part . value
286+ }
181287 return this . #select( fmt , this . #type === 'ordinal' )
182288 }
183289
0 commit comments