@formatjs/intl-pluralrules 6.1.1 → 6.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/abstract/GetOperands.d.ts +56 -29
- package/abstract/GetOperands.js +48 -44
- package/abstract/InitializePluralRules.d.ts +7 -7
- package/abstract/InitializePluralRules.js +30 -16
- package/abstract/ResolvePlural.d.ts +41 -11
- package/abstract/ResolvePlural.js +73 -20
- package/abstract/ResolvePluralRange.d.ts +23 -0
- package/abstract/ResolvePluralRange.js +59 -0
- package/get_internal_slots.d.ts +2 -1
- package/get_internal_slots.js +8 -7
- package/index.d.ts +75 -17
- package/index.js +217 -137
- package/locale-data/af.js +14 -4
- package/locale-data/ak.js +14 -4
- package/locale-data/am.js +15 -4
- package/locale-data/an.js +14 -4
- package/locale-data/ar.js +22 -10
- package/locale-data/ars.js +21 -9
- package/locale-data/as.js +23 -8
- package/locale-data/asa.js +13 -3
- package/locale-data/ast.js +14 -4
- package/locale-data/az.js +21 -8
- package/locale-data/bal.js +15 -2
- package/locale-data/be.js +20 -8
- package/locale-data/bem.js +13 -3
- package/locale-data/bez.js +13 -3
- package/locale-data/bg.js +14 -4
- package/locale-data/bho.js +13 -3
- package/locale-data/bm.js +2 -2
- package/locale-data/bn.js +23 -8
- package/locale-data/bo.js +2 -2
- package/locale-data/br.js +19 -8
- package/locale-data/brx.js +13 -3
- package/locale-data/bs.js +18 -7
- package/locale-data/ca.js +25 -10
- package/locale-data/ce.js +13 -3
- package/locale-data/ceb.js +15 -4
- package/locale-data/cgg.js +13 -3
- package/locale-data/chr.js +13 -3
- package/locale-data/ckb.js +13 -3
- package/locale-data/cs.js +19 -8
- package/locale-data/cy.js +32 -14
- package/locale-data/da.js +17 -5
- package/locale-data/de.js +15 -5
- package/locale-data/doi.js +14 -3
- package/locale-data/dsb.js +19 -7
- package/locale-data/dv.js +13 -3
- package/locale-data/dz.js +2 -2
- package/locale-data/ee.js +13 -3
- package/locale-data/el.js +14 -4
- package/locale-data/en.js +22 -8
- package/locale-data/eo.js +13 -3
- package/locale-data/es.js +19 -7
- package/locale-data/et.js +15 -5
- package/locale-data/eu.js +14 -4
- package/locale-data/fa.js +15 -4
- package/locale-data/ff.js +13 -3
- package/locale-data/fi.js +15 -5
- package/locale-data/fil.js +19 -5
- package/locale-data/fo.js +13 -3
- package/locale-data/fr.js +21 -7
- package/locale-data/fur.js +13 -3
- package/locale-data/fy.js +14 -4
- package/locale-data/ga.js +22 -9
- package/locale-data/gd.js +23 -10
- package/locale-data/gl.js +15 -5
- package/locale-data/gsw.js +14 -4
- package/locale-data/gu.js +23 -8
- package/locale-data/guw.js +13 -3
- package/locale-data/gv.js +20 -8
- package/locale-data/ha.js +13 -3
- package/locale-data/haw.js +13 -3
- package/locale-data/he.js +17 -7
- package/locale-data/hi.js +23 -8
- package/locale-data/hnj.js +2 -2
- package/locale-data/hr.js +18 -7
- package/locale-data/hsb.js +19 -7
- package/locale-data/hu.js +16 -4
- package/locale-data/hy.js +17 -4
- package/locale-data/ia.js +15 -5
- package/locale-data/id.js +3 -3
- package/locale-data/ig.js +2 -2
- package/locale-data/ii.js +2 -2
- package/locale-data/io.js +15 -5
- package/locale-data/is.js +16 -5
- package/locale-data/it.js +21 -7
- package/locale-data/iu.js +15 -5
- package/locale-data/ja.js +3 -3
- package/locale-data/jbo.js +2 -2
- package/locale-data/jgo.js +13 -3
- package/locale-data/jmc.js +13 -3
- package/locale-data/jv.js +2 -2
- package/locale-data/jw.js +2 -2
- package/locale-data/ka.js +19 -7
- package/locale-data/kab.js +13 -3
- package/locale-data/kaj.js +13 -3
- package/locale-data/kcg.js +13 -3
- package/locale-data/kde.js +2 -2
- package/locale-data/kea.js +2 -2
- package/locale-data/kk.js +16 -5
- package/locale-data/kkj.js +13 -3
- package/locale-data/kl.js +13 -3
- package/locale-data/km.js +3 -3
- package/locale-data/kn.js +15 -4
- package/locale-data/ko.js +3 -3
- package/locale-data/ks.js +13 -3
- package/locale-data/ksb.js +13 -3
- package/locale-data/ksh.js +15 -5
- package/locale-data/ku.js +13 -3
- package/locale-data/kw.js +25 -11
- package/locale-data/ky.js +14 -4
- package/locale-data/lag.js +16 -6
- package/locale-data/lb.js +13 -3
- package/locale-data/lg.js +13 -3
- package/locale-data/lij.js +18 -5
- package/locale-data/lkt.js +2 -2
- package/locale-data/ln.js +13 -3
- package/locale-data/lo.js +14 -4
- package/locale-data/lt.js +20 -8
- package/locale-data/lv.js +18 -7
- package/locale-data/mas.js +13 -3
- package/locale-data/mg.js +13 -3
- package/locale-data/mgo.js +13 -3
- package/locale-data/mk.js +22 -8
- package/locale-data/ml.js +14 -4
- package/locale-data/mn.js +14 -4
- package/locale-data/mo.js +19 -6
- package/locale-data/mr.js +20 -7
- package/locale-data/ms.js +14 -4
- package/locale-data/mt.js +19 -8
- package/locale-data/my.js +3 -3
- package/locale-data/nah.js +13 -3
- package/locale-data/naq.js +15 -5
- package/locale-data/nb.js +14 -4
- package/locale-data/nd.js +13 -3
- package/locale-data/ne.js +16 -5
- package/locale-data/nl.js +15 -5
- package/locale-data/nn.js +13 -3
- package/locale-data/nnh.js +13 -3
- package/locale-data/no.js +14 -4
- package/locale-data/nqo.js +2 -2
- package/locale-data/nr.js +13 -3
- package/locale-data/nso.js +13 -3
- package/locale-data/ny.js +13 -3
- package/locale-data/nyn.js +13 -3
- package/locale-data/om.js +13 -3
- package/locale-data/or.js +22 -9
- package/locale-data/os.js +13 -3
- package/locale-data/osa.js +2 -2
- package/locale-data/pa.js +14 -4
- package/locale-data/pap.js +13 -3
- package/locale-data/pcm.js +15 -4
- package/locale-data/pl.js +19 -8
- package/locale-data/prg.js +17 -6
- package/locale-data/ps.js +14 -4
- package/locale-data/pt-PT.js +17 -6
- package/locale-data/pt.js +18 -7
- package/locale-data/rm.js +13 -3
- package/locale-data/ro.js +20 -7
- package/locale-data/rof.js +13 -3
- package/locale-data/ru.js +19 -8
- package/locale-data/rwk.js +13 -3
- package/locale-data/sah.js +2 -2
- package/locale-data/saq.js +13 -3
- package/locale-data/sat.js +15 -5
- package/locale-data/sc.js +18 -5
- package/locale-data/scn.js +21 -7
- package/locale-data/sd.js +14 -4
- package/locale-data/sdh.js +13 -3
- package/locale-data/se.js +15 -5
- package/locale-data/seh.js +13 -3
- package/locale-data/ses.js +2 -2
- package/locale-data/sg.js +2 -2
- package/locale-data/sh.js +17 -6
- package/locale-data/shi.js +16 -6
- package/locale-data/si.js +17 -5
- package/locale-data/sk.js +19 -8
- package/locale-data/sl.js +19 -8
- package/locale-data/sma.js +15 -5
- package/locale-data/smi.js +15 -5
- package/locale-data/smj.js +15 -5
- package/locale-data/smn.js +15 -5
- package/locale-data/sms.js +15 -5
- package/locale-data/sn.js +13 -3
- package/locale-data/so.js +13 -3
- package/locale-data/sq.js +18 -7
- package/locale-data/sr.js +18 -7
- package/locale-data/ss.js +13 -3
- package/locale-data/ssy.js +13 -3
- package/locale-data/st.js +13 -3
- package/locale-data/su.js +2 -2
- package/locale-data/sv.js +18 -5
- package/locale-data/sw.js +15 -5
- package/locale-data/syr.js +13 -3
- package/locale-data/ta.js +14 -4
- package/locale-data/te.js +14 -4
- package/locale-data/teo.js +13 -3
- package/locale-data/th.js +3 -3
- package/locale-data/ti.js +13 -3
- package/locale-data/tig.js +13 -3
- package/locale-data/tk.js +16 -5
- package/locale-data/tl.js +18 -4
- package/locale-data/tn.js +13 -3
- package/locale-data/to.js +2 -2
- package/locale-data/tpi.js +2 -2
- package/locale-data/tr.js +14 -4
- package/locale-data/ts.js +13 -3
- package/locale-data/tzm.js +13 -4
- package/locale-data/ug.js +14 -4
- package/locale-data/uk.js +22 -8
- package/locale-data/und.js +2 -2
- package/locale-data/ur.js +15 -5
- package/locale-data/uz.js +14 -4
- package/locale-data/ve.js +13 -3
- package/locale-data/vi.js +14 -4
- package/locale-data/vo.js +13 -3
- package/locale-data/vun.js +13 -3
- package/locale-data/wa.js +13 -3
- package/locale-data/wae.js +13 -3
- package/locale-data/wo.js +2 -2
- package/locale-data/xh.js +13 -3
- package/locale-data/xog.js +13 -3
- package/locale-data/yi.js +14 -4
- package/locale-data/yo.js +2 -2
- package/locale-data/yue.js +3 -3
- package/locale-data/zh.js +3 -3
- package/locale-data/zu.js +15 -4
- package/package.json +7 -7
- package/polyfill-force.js +6 -6
- package/polyfill.iife.js +2599 -3121
- package/polyfill.js +8 -8
- package/should-polyfill.js +11 -15
- package/supported-locales.generated.js +216 -216
|
@@ -1,32 +1,59 @@
|
|
|
1
|
-
import Decimal from
|
|
1
|
+
import type Decimal from "decimal.js";
|
|
2
|
+
/**
|
|
3
|
+
* CLDR Spec: Operands as defined in https://unicode.org/reports/tr35/tr35-numbers.html#Operands
|
|
4
|
+
* ECMA-402 Spec: GetOperands abstract operation (https://tc39.es/ecma402/#sec-getoperands)
|
|
5
|
+
*
|
|
6
|
+
* Maps CLDR operand symbols to JavaScript property names:
|
|
7
|
+
* - n → Number (absolute value)
|
|
8
|
+
* - i → IntegerDigits
|
|
9
|
+
* - v → NumberOfFractionDigits
|
|
10
|
+
* - w → NumberOfFractionDigitsWithoutTrailing
|
|
11
|
+
* - f → FractionDigits
|
|
12
|
+
* - t → FractionDigitsWithoutTrailing
|
|
13
|
+
* - c, e → CompactExponent (extension for compact notation)
|
|
14
|
+
*/
|
|
2
15
|
export interface OperandsRecord {
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
16
|
+
/**
|
|
17
|
+
* CLDR operand: n (absolute value of the source number)
|
|
18
|
+
*/
|
|
19
|
+
Number: Decimal;
|
|
20
|
+
/**
|
|
21
|
+
* CLDR operand: i (integer digits of n)
|
|
22
|
+
* Implementation: String for very large numbers exceeding Number.MAX_SAFE_INTEGER
|
|
23
|
+
*/
|
|
24
|
+
IntegerDigits: number | string;
|
|
25
|
+
/**
|
|
26
|
+
* CLDR operand: v (number of visible fraction digits in n, with trailing zeros)
|
|
27
|
+
*/
|
|
28
|
+
NumberOfFractionDigits: number;
|
|
29
|
+
/**
|
|
30
|
+
* CLDR operand: w (number of visible fraction digits in n, without trailing zeros)
|
|
31
|
+
*/
|
|
32
|
+
NumberOfFractionDigitsWithoutTrailing: number;
|
|
33
|
+
/**
|
|
34
|
+
* CLDR operand: f (visible fractional digits in n, with trailing zeros)
|
|
35
|
+
*/
|
|
36
|
+
FractionDigits: number;
|
|
37
|
+
/**
|
|
38
|
+
* CLDR operand: t (visible fractional digits in n, without trailing zeros)
|
|
39
|
+
*/
|
|
40
|
+
FractionDigitsWithoutTrailing: number;
|
|
41
|
+
/**
|
|
42
|
+
* CLDR operands: c and e (synonyms for compact decimal exponent)
|
|
43
|
+
*
|
|
44
|
+
* Extension: Not in base ECMA-402 spec, but defined in CLDR for compact notation.
|
|
45
|
+
* Example: "1.2M" has exponent 6 (since M = 10^6)
|
|
46
|
+
* Used by 9 locales: ca, es, fr, it, lld, pt, pt-PT, scn, vec
|
|
47
|
+
*/
|
|
48
|
+
CompactExponent: number;
|
|
27
49
|
}
|
|
28
50
|
/**
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
51
|
+
* ECMA-402 Spec: GetOperands abstract operation
|
|
52
|
+
* https://tc39.es/ecma402/#sec-getoperands
|
|
53
|
+
*
|
|
54
|
+
* Implementation: Extended to support compact exponent (c/e operands)
|
|
55
|
+
*
|
|
56
|
+
* @param s Formatted number string
|
|
57
|
+
* @param exponent Compact decimal exponent (c/e operand), defaults to 0
|
|
58
|
+
*/
|
|
59
|
+
export declare function GetOperands(s: string, exponent?: number): OperandsRecord;
|
package/abstract/GetOperands.js
CHANGED
|
@@ -1,46 +1,50 @@
|
|
|
1
|
-
import { invariant, ToNumber, ZERO } from
|
|
1
|
+
import { invariant, ToNumber, ZERO } from "@formatjs/ecma402-abstract";
|
|
2
2
|
/**
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
3
|
+
* ECMA-402 Spec: GetOperands abstract operation
|
|
4
|
+
* https://tc39.es/ecma402/#sec-getoperands
|
|
5
|
+
*
|
|
6
|
+
* Implementation: Extended to support compact exponent (c/e operands)
|
|
7
|
+
*
|
|
8
|
+
* @param s Formatted number string
|
|
9
|
+
* @param exponent Compact decimal exponent (c/e operand), defaults to 0
|
|
10
|
+
*/
|
|
11
|
+
export function GetOperands(s, exponent = 0) {
|
|
12
|
+
invariant(typeof s === "string", `GetOperands should have been called with a string`);
|
|
13
|
+
const n = ToNumber(s);
|
|
14
|
+
invariant(n.isFinite(), "n should be finite");
|
|
15
|
+
let dp = s.indexOf(".");
|
|
16
|
+
let iv;
|
|
17
|
+
let f;
|
|
18
|
+
let v;
|
|
19
|
+
let fv = "";
|
|
20
|
+
if (dp === -1) {
|
|
21
|
+
iv = n;
|
|
22
|
+
f = ZERO;
|
|
23
|
+
v = 0;
|
|
24
|
+
} else {
|
|
25
|
+
iv = s.slice(0, dp);
|
|
26
|
+
fv = s.slice(dp, s.length);
|
|
27
|
+
f = ToNumber(fv);
|
|
28
|
+
v = fv.length;
|
|
29
|
+
}
|
|
30
|
+
const i = ToNumber(iv).abs();
|
|
31
|
+
let w;
|
|
32
|
+
let t;
|
|
33
|
+
if (!f.isZero()) {
|
|
34
|
+
const ft = fv.replace(/0+$/, "");
|
|
35
|
+
w = ft.length;
|
|
36
|
+
t = ToNumber(ft);
|
|
37
|
+
} else {
|
|
38
|
+
w = 0;
|
|
39
|
+
t = ZERO;
|
|
40
|
+
}
|
|
41
|
+
return {
|
|
42
|
+
Number: n,
|
|
43
|
+
IntegerDigits: i.lessThanOrEqualTo(Number.MAX_SAFE_INTEGER) && i.greaterThanOrEqualTo(-Number.MAX_SAFE_INTEGER) ? i.toNumber() : i.toString(),
|
|
44
|
+
NumberOfFractionDigits: v,
|
|
45
|
+
NumberOfFractionDigitsWithoutTrailing: w,
|
|
46
|
+
FractionDigits: f.toNumber(),
|
|
47
|
+
FractionDigitsWithoutTrailing: t.toNumber(),
|
|
48
|
+
CompactExponent: exponent
|
|
49
|
+
};
|
|
46
50
|
}
|
|
@@ -1,8 +1,8 @@
|
|
|
1
|
-
import { PluralRulesData, PluralRulesInternal } from
|
|
2
|
-
export declare function InitializePluralRules(pl: Intl.PluralRules, locales: string | string[] | undefined, options: Intl.PluralRulesOptions | undefined, { availableLocales, relevantExtensionKeys, localeData, getDefaultLocale, getInternalSlots
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
1
|
+
import { type PluralRulesData, type PluralRulesInternal } from "@formatjs/ecma402-abstract";
|
|
2
|
+
export declare function InitializePluralRules(pl: Intl.PluralRules, locales: string | string[] | undefined, options: Intl.PluralRulesOptions | undefined, { availableLocales, relevantExtensionKeys, localeData, getDefaultLocale, getInternalSlots }: {
|
|
3
|
+
availableLocales: Set<string>;
|
|
4
|
+
relevantExtensionKeys: string[];
|
|
5
|
+
localeData: Record<string, PluralRulesData | undefined>;
|
|
6
|
+
getDefaultLocale(): string;
|
|
7
|
+
getInternalSlots(pl: Intl.PluralRules): PluralRulesInternal;
|
|
8
8
|
}): Intl.PluralRules;
|
|
@@ -1,17 +1,31 @@
|
|
|
1
|
-
import { CanonicalizeLocaleList, CoerceOptionsToObject, GetOption, SetNumberFormatDigitOptions
|
|
2
|
-
import { ResolveLocale } from
|
|
3
|
-
export function InitializePluralRules(pl, locales, options,
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
1
|
+
import { CanonicalizeLocaleList, CoerceOptionsToObject, GetOption, SetNumberFormatDigitOptions } from "@formatjs/ecma402-abstract";
|
|
2
|
+
import { ResolveLocale } from "@formatjs/intl-localematcher";
|
|
3
|
+
export function InitializePluralRules(pl, locales, options, { availableLocales, relevantExtensionKeys, localeData, getDefaultLocale, getInternalSlots }) {
|
|
4
|
+
const requestedLocales = CanonicalizeLocaleList(locales);
|
|
5
|
+
const opt = Object.create(null);
|
|
6
|
+
const opts = CoerceOptionsToObject(options);
|
|
7
|
+
const internalSlots = getInternalSlots(pl);
|
|
8
|
+
internalSlots.initializedPluralRules = true;
|
|
9
|
+
const matcher = GetOption(opts, "localeMatcher", "string", ["best fit", "lookup"], "best fit");
|
|
10
|
+
opt.localeMatcher = matcher;
|
|
11
|
+
const r = ResolveLocale(availableLocales, requestedLocales, opt, relevantExtensionKeys, localeData, getDefaultLocale);
|
|
12
|
+
internalSlots.locale = r.locale;
|
|
13
|
+
// ECMA-402 Spec: type option ('cardinal' or 'ordinal')
|
|
14
|
+
internalSlots.type = GetOption(opts, "type", "string", ["cardinal", "ordinal"], "cardinal");
|
|
15
|
+
// Extension: notation options for compact notation support
|
|
16
|
+
// Not in ECMA-402 spec, but mirrors Intl.NumberFormat notation option
|
|
17
|
+
// Enables proper plural selection for compact numbers (e.g., "1.2M")
|
|
18
|
+
const notation = GetOption(opts, "notation", "string", ["standard", "compact"], "standard");
|
|
19
|
+
internalSlots.notation = notation;
|
|
20
|
+
if (notation === "compact") {
|
|
21
|
+
// Extension: compactDisplay option (mirrors Intl.NumberFormat)
|
|
22
|
+
internalSlots.compactDisplay = GetOption(opts, "compactDisplay", "string", ["short", "long"], "short");
|
|
23
|
+
// Implementation: Load NumberFormat locale data if available (soft dependency)
|
|
24
|
+
// This is needed to calculate compact exponents using ComputeExponentForMagnitude
|
|
25
|
+
if (typeof Intl !== "undefined" && Intl.NumberFormat && Intl.NumberFormat.localeData) {
|
|
26
|
+
internalSlots.dataLocaleData = Intl.NumberFormat.localeData[r.locale];
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
SetNumberFormatDigitOptions(internalSlots, opts, 0, 3, "standard");
|
|
30
|
+
return pl;
|
|
17
31
|
}
|
|
@@ -1,13 +1,43 @@
|
|
|
1
|
-
import { LDMLPluralRule, PluralRulesInternal } from
|
|
2
|
-
import Decimal from
|
|
3
|
-
import { OperandsRecord } from
|
|
1
|
+
import { type LDMLPluralRule, type PluralRulesInternal } from "@formatjs/ecma402-abstract";
|
|
2
|
+
import Decimal from "decimal.js";
|
|
3
|
+
import { type OperandsRecord } from "./GetOperands.js";
|
|
4
4
|
/**
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
5
|
+
* Result of ResolvePluralInternal containing both the formatted string and plural category.
|
|
6
|
+
* This corresponds to a Record with [[FormattedString]] and [[PluralCategory]] fields
|
|
7
|
+
* as described in the ECMA-402 spec for ResolvePluralRange.
|
|
8
|
+
*/
|
|
9
|
+
export interface ResolvePluralResult {
|
|
10
|
+
/** The formatted representation of the number */
|
|
11
|
+
formattedString: string;
|
|
12
|
+
/** The LDML plural category (zero, one, two, few, many, or other) */
|
|
13
|
+
pluralCategory: LDMLPluralRule;
|
|
14
|
+
}
|
|
15
|
+
/**
|
|
16
|
+
* ResolvePluralInternal ( pluralRules, n )
|
|
17
|
+
*
|
|
18
|
+
* Internal version of ResolvePlural that returns both the formatted string and plural category.
|
|
19
|
+
* This is needed for selectRange, which must compare formatted strings to determine if the
|
|
20
|
+
* start and end values are identical.
|
|
21
|
+
*
|
|
22
|
+
* The formatted string is obtained by applying the number formatting options (digit options)
|
|
23
|
+
* from the PluralRules object to the input number. This ensures that formatting-sensitive
|
|
24
|
+
* plural rules work correctly (e.g., rules that depend on visible fraction digits).
|
|
25
|
+
*
|
|
26
|
+
* @param pl - An initialized PluralRules object
|
|
27
|
+
* @param n - Mathematical value to resolve
|
|
28
|
+
* @returns Record containing the formatted string and plural category
|
|
29
|
+
*/
|
|
30
|
+
export declare function ResolvePluralInternal(pl: Intl.PluralRules, n: Decimal, { getInternalSlots, PluralRuleSelect }: {
|
|
31
|
+
getInternalSlots(pl: Intl.PluralRules): PluralRulesInternal;
|
|
32
|
+
PluralRuleSelect: (locale: string, type: "cardinal" | "ordinal", n: Decimal, operands: OperandsRecord) => LDMLPluralRule;
|
|
33
|
+
}): ResolvePluralResult;
|
|
34
|
+
/**
|
|
35
|
+
* http://ecma-international.org/ecma-402/7.0/index.html#sec-resolveplural
|
|
36
|
+
* @param pl
|
|
37
|
+
* @param n
|
|
38
|
+
* @param PluralRuleSelect Has to pass in bc it's implementation-specific
|
|
39
|
+
*/
|
|
40
|
+
export declare function ResolvePlural(pl: Intl.PluralRules, n: Decimal, { getInternalSlots, PluralRuleSelect }: {
|
|
41
|
+
getInternalSlots(pl: Intl.PluralRules): PluralRulesInternal;
|
|
42
|
+
PluralRuleSelect: (locale: string, type: "cardinal" | "ordinal", n: Decimal, operands: OperandsRecord) => LDMLPluralRule;
|
|
13
43
|
}): LDMLPluralRule;
|
|
@@ -1,22 +1,75 @@
|
|
|
1
|
-
import { FormatNumericToString, invariant, Type
|
|
2
|
-
import
|
|
1
|
+
import { ComputeExponentForMagnitude, FormatNumericToString, invariant, Type } from "@formatjs/ecma402-abstract";
|
|
2
|
+
import Decimal from "decimal.js";
|
|
3
|
+
import { GetOperands } from "./GetOperands.js";
|
|
3
4
|
/**
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
5
|
+
* ResolvePluralInternal ( pluralRules, n )
|
|
6
|
+
*
|
|
7
|
+
* Internal version of ResolvePlural that returns both the formatted string and plural category.
|
|
8
|
+
* This is needed for selectRange, which must compare formatted strings to determine if the
|
|
9
|
+
* start and end values are identical.
|
|
10
|
+
*
|
|
11
|
+
* The formatted string is obtained by applying the number formatting options (digit options)
|
|
12
|
+
* from the PluralRules object to the input number. This ensures that formatting-sensitive
|
|
13
|
+
* plural rules work correctly (e.g., rules that depend on visible fraction digits).
|
|
14
|
+
*
|
|
15
|
+
* @param pl - An initialized PluralRules object
|
|
16
|
+
* @param n - Mathematical value to resolve
|
|
17
|
+
* @returns Record containing the formatted string and plural category
|
|
18
|
+
*/
|
|
19
|
+
export function ResolvePluralInternal(pl, n, { getInternalSlots, PluralRuleSelect }) {
|
|
20
|
+
const internalSlots = getInternalSlots(pl);
|
|
21
|
+
invariant(Type(internalSlots) === "Object", "pl has to be an object");
|
|
22
|
+
invariant("initializedPluralRules" in internalSlots, "pluralrules must be initialized");
|
|
23
|
+
// Handle non-finite values (Infinity, -Infinity, NaN)
|
|
24
|
+
if (!n.isFinite()) {
|
|
25
|
+
return {
|
|
26
|
+
formattedString: String(n),
|
|
27
|
+
pluralCategory: "other"
|
|
28
|
+
};
|
|
29
|
+
}
|
|
30
|
+
const { locale, type, notation } = internalSlots;
|
|
31
|
+
// ECMA-402 Spec: Format the number according to digit options
|
|
32
|
+
const res = FormatNumericToString(internalSlots, n);
|
|
33
|
+
const s = res.formattedString;
|
|
34
|
+
// Extension: Calculate compact exponent if using compact notation
|
|
35
|
+
// This enables CLDR c/e operands for proper plural selection with compact numbers
|
|
36
|
+
let exponent = 0;
|
|
37
|
+
if (notation === "compact" && !n.isZero()) {
|
|
38
|
+
// Implementation: Only calculate exponent if NumberFormat locale data is available (soft dependency)
|
|
39
|
+
if (internalSlots.dataLocaleData?.numbers) {
|
|
40
|
+
try {
|
|
41
|
+
// Calculate magnitude (floor of log10 of absolute value)
|
|
42
|
+
const magnitudeNum = Math.floor(Math.log10(Math.abs(n.toNumber())));
|
|
43
|
+
const magnitude = new Decimal(magnitudeNum);
|
|
44
|
+
// Use ComputeExponentForMagnitude from ecma402-abstract
|
|
45
|
+
// This determines which compact notation pattern to use (K, M, B, etc.)
|
|
46
|
+
// Cast to any since it expects NumberFormatInternal
|
|
47
|
+
exponent = ComputeExponentForMagnitude(internalSlots, magnitude);
|
|
48
|
+
} catch {
|
|
49
|
+
// Gracefully fall back to 0 if exponent calculation fails
|
|
50
|
+
exponent = 0;
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
// ECMA-402 Spec: Extract CLDR operands from the formatted string
|
|
55
|
+
// Extension: Pass exponent for c/e operands
|
|
56
|
+
const operands = GetOperands(s, exponent);
|
|
57
|
+
// ECMA-402 Spec: Select the appropriate plural category using the locale's plural rules
|
|
58
|
+
const pluralCategory = PluralRuleSelect(locale, type, n, operands);
|
|
59
|
+
return {
|
|
60
|
+
formattedString: s,
|
|
61
|
+
pluralCategory
|
|
62
|
+
};
|
|
63
|
+
}
|
|
64
|
+
/**
|
|
65
|
+
* http://ecma-international.org/ecma-402/7.0/index.html#sec-resolveplural
|
|
66
|
+
* @param pl
|
|
67
|
+
* @param n
|
|
68
|
+
* @param PluralRuleSelect Has to pass in bc it's implementation-specific
|
|
69
|
+
*/
|
|
70
|
+
export function ResolvePlural(pl, n, { getInternalSlots, PluralRuleSelect }) {
|
|
71
|
+
return ResolvePluralInternal(pl, n, {
|
|
72
|
+
getInternalSlots,
|
|
73
|
+
PluralRuleSelect
|
|
74
|
+
}).pluralCategory;
|
|
22
75
|
}
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import { type LDMLPluralRule, type PluralRulesInternal } from "@formatjs/ecma402-abstract";
|
|
2
|
+
import type Decimal from "decimal.js";
|
|
3
|
+
import { type OperandsRecord } from "./GetOperands.js";
|
|
4
|
+
/**
|
|
5
|
+
* ResolvePluralRange ( pluralRules, x, y )
|
|
6
|
+
*
|
|
7
|
+
* The ResolvePluralRange abstract operation is called with arguments pluralRules (which must be
|
|
8
|
+
* an object initialized as a PluralRules), x (a mathematical value), and y (a mathematical value).
|
|
9
|
+
* It resolves the appropriate plural form for a range by determining the plural forms of both the
|
|
10
|
+
* start and end values, then consulting locale-specific range data.
|
|
11
|
+
*
|
|
12
|
+
* Specification: https://tc39.es/ecma402/#sec-resolvepluralrange
|
|
13
|
+
*
|
|
14
|
+
* @param pluralRules - An initialized PluralRules object
|
|
15
|
+
* @param x - Mathematical value for the range start
|
|
16
|
+
* @param y - Mathematical value for the range end
|
|
17
|
+
* @returns The plural category for the range (zero, one, two, few, many, or other)
|
|
18
|
+
*/
|
|
19
|
+
export declare function ResolvePluralRange(pluralRules: Intl.PluralRules, x: Decimal, y: Decimal, { getInternalSlots, PluralRuleSelect, PluralRuleSelectRange }: {
|
|
20
|
+
getInternalSlots(pl: Intl.PluralRules): PluralRulesInternal;
|
|
21
|
+
PluralRuleSelect: (locale: string, type: "cardinal" | "ordinal", n: Decimal, operands: OperandsRecord) => LDMLPluralRule;
|
|
22
|
+
PluralRuleSelectRange: (locale: string, type: "cardinal" | "ordinal", xp: LDMLPluralRule, yp: LDMLPluralRule) => LDMLPluralRule;
|
|
23
|
+
}): LDMLPluralRule;
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
import { invariant, Type } from "@formatjs/ecma402-abstract";
|
|
2
|
+
import "./GetOperands.js";
|
|
3
|
+
import { ResolvePluralInternal } from "./ResolvePlural.js";
|
|
4
|
+
/**
|
|
5
|
+
* ResolvePluralRange ( pluralRules, x, y )
|
|
6
|
+
*
|
|
7
|
+
* The ResolvePluralRange abstract operation is called with arguments pluralRules (which must be
|
|
8
|
+
* an object initialized as a PluralRules), x (a mathematical value), and y (a mathematical value).
|
|
9
|
+
* It resolves the appropriate plural form for a range by determining the plural forms of both the
|
|
10
|
+
* start and end values, then consulting locale-specific range data.
|
|
11
|
+
*
|
|
12
|
+
* Specification: https://tc39.es/ecma402/#sec-resolvepluralrange
|
|
13
|
+
*
|
|
14
|
+
* @param pluralRules - An initialized PluralRules object
|
|
15
|
+
* @param x - Mathematical value for the range start
|
|
16
|
+
* @param y - Mathematical value for the range end
|
|
17
|
+
* @returns The plural category for the range (zero, one, two, few, many, or other)
|
|
18
|
+
*/
|
|
19
|
+
export function ResolvePluralRange(pluralRules, x, y, { getInternalSlots, PluralRuleSelect, PluralRuleSelectRange }) {
|
|
20
|
+
// 1. If x is not-a-number or y is not-a-number, throw a RangeError exception.
|
|
21
|
+
if (!x.isFinite() || !y.isFinite()) {
|
|
22
|
+
throw new RangeError("selectRange requires start and end values to be finite numbers");
|
|
23
|
+
}
|
|
24
|
+
// Validation: Assert that pluralRules has been initialized
|
|
25
|
+
const internalSlots = getInternalSlots(pluralRules);
|
|
26
|
+
invariant(Type(internalSlots) === "Object", "pluralRules has to be an object");
|
|
27
|
+
invariant("initializedPluralRules" in internalSlots, "pluralrules must be initialized");
|
|
28
|
+
// 2. Let xp be ResolvePlural(pluralRules, x).
|
|
29
|
+
// Note: ResolvePlural returns a Record with [[FormattedString]] and [[PluralCategory]]
|
|
30
|
+
const xp = ResolvePluralInternal(pluralRules, x, {
|
|
31
|
+
getInternalSlots,
|
|
32
|
+
PluralRuleSelect
|
|
33
|
+
});
|
|
34
|
+
// 3. Let yp be ResolvePlural(pluralRules, y).
|
|
35
|
+
const yp = ResolvePluralInternal(pluralRules, y, {
|
|
36
|
+
getInternalSlots,
|
|
37
|
+
PluralRuleSelect
|
|
38
|
+
});
|
|
39
|
+
// 4. If xp.[[FormattedString]] is yp.[[FormattedString]], then
|
|
40
|
+
// a. Return xp.[[PluralCategory]].
|
|
41
|
+
// Note: When the formatted strings are identical (e.g., "1" and "1"), the values are
|
|
42
|
+
// effectively the same, so we return the plural category of the start value.
|
|
43
|
+
if (xp.formattedString === yp.formattedString) {
|
|
44
|
+
return xp.pluralCategory;
|
|
45
|
+
}
|
|
46
|
+
// 5. Let locale be pluralRules.[[Locale]].
|
|
47
|
+
// 6. Let type be pluralRules.[[Type]].
|
|
48
|
+
const { locale, type } = internalSlots;
|
|
49
|
+
// 7. Let notation be pluralRules.[[Notation]].
|
|
50
|
+
// 8. Let compactDisplay be pluralRules.[[CompactDisplay]].
|
|
51
|
+
// Note: notation and compactDisplay are not yet implemented for PluralRules polyfill.
|
|
52
|
+
// When implemented, these would affect how the range is formatted and thus which
|
|
53
|
+
// plural rules apply (see c/e operand support).
|
|
54
|
+
// 9. Return PluralRuleSelectRange(locale, type, notation, compactDisplay, xp.[[PluralCategory]], yp.[[PluralCategory]]).
|
|
55
|
+
// Note: PluralRuleSelectRange is implementation-defined and uses CLDR plural range data
|
|
56
|
+
// to determine the appropriate plural category for a range based on the start and end categories.
|
|
57
|
+
// Example: In English, "one" to "other" → "other" (e.g., "1-2 items")
|
|
58
|
+
return PluralRuleSelectRange(locale, type, xp.pluralCategory, yp.pluralCategory);
|
|
59
|
+
}
|
package/get_internal_slots.d.ts
CHANGED
package/get_internal_slots.js
CHANGED
|
@@ -1,9 +1,10 @@
|
|
|
1
|
-
|
|
1
|
+
import "./index.js";
|
|
2
|
+
const internalSlotMap = new WeakMap();
|
|
2
3
|
export default function getInternalSlots(x) {
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
4
|
+
let internalSlots = internalSlotMap.get(x);
|
|
5
|
+
if (!internalSlots) {
|
|
6
|
+
internalSlots = Object.create(null);
|
|
7
|
+
internalSlotMap.set(x, internalSlots);
|
|
8
|
+
}
|
|
9
|
+
return internalSlots;
|
|
9
10
|
}
|
package/index.d.ts
CHANGED
|
@@ -1,20 +1,78 @@
|
|
|
1
|
-
import { LDMLPluralRule, NumberFormatDigitInternalSlots, PluralRulesData, PluralRulesLocaleData } from
|
|
1
|
+
import { type LDMLPluralRule, type NumberFormatDigitInternalSlots, type PluralRulesData, type PluralRulesLocaleData } from "@formatjs/ecma402-abstract";
|
|
2
|
+
/**
|
|
3
|
+
* Type augmentation for Intl.PluralRules
|
|
4
|
+
*
|
|
5
|
+
* ECMA-402 Spec: selectRange method (Intl.PluralRules.prototype.selectRange)
|
|
6
|
+
* https://tc39.es/ecma402/#sec-intl.pluralrules.prototype.selectrange
|
|
7
|
+
*
|
|
8
|
+
* Extension: notation and compactDisplay options (not in ECMA-402 spec)
|
|
9
|
+
* Mirrors Intl.NumberFormat notation option for proper plural selection with compact numbers
|
|
10
|
+
*/
|
|
11
|
+
declare global {
|
|
12
|
+
namespace Intl {
|
|
13
|
+
interface PluralRules {
|
|
14
|
+
selectRange(start: number | bigint, end: number | bigint): LDMLPluralRule;
|
|
15
|
+
}
|
|
16
|
+
interface PluralRulesOptions {
|
|
17
|
+
notation?: "standard" | "compact";
|
|
18
|
+
compactDisplay?: "short" | "long";
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
}
|
|
2
22
|
export interface PluralRulesInternal extends NumberFormatDigitInternalSlots {
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
23
|
+
initializedPluralRules: boolean;
|
|
24
|
+
locale: string;
|
|
25
|
+
type: "cardinal" | "ordinal";
|
|
26
|
+
notation: "standard" | "compact";
|
|
27
|
+
compactDisplay?: "short" | "long";
|
|
28
|
+
dataLocaleData?: any;
|
|
6
29
|
}
|
|
7
|
-
export declare class PluralRules
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
30
|
+
export declare class PluralRules {
|
|
31
|
+
constructor(locales?: string | string[], options?: Intl.PluralRulesOptions);
|
|
32
|
+
resolvedOptions(): Intl.ResolvedPluralRulesOptions;
|
|
33
|
+
select(val: number | bigint): LDMLPluralRule;
|
|
34
|
+
/**
|
|
35
|
+
* Intl.PluralRules.prototype.selectRange ( start, end )
|
|
36
|
+
*
|
|
37
|
+
* Returns a string indicating which plural rule applies to a range of numbers.
|
|
38
|
+
* This is useful for formatting ranges like "1-2 items" vs "2-3 items" where
|
|
39
|
+
* different languages have different plural rules for ranges.
|
|
40
|
+
*
|
|
41
|
+
* Specification: https://tc39.es/ecma402/#sec-intl.pluralrules.prototype.selectrange
|
|
42
|
+
*
|
|
43
|
+
* @param start - The start value of the range (number or bigint)
|
|
44
|
+
* @param end - The end value of the range (number or bigint)
|
|
45
|
+
* @returns The plural category for the range (zero, one, two, few, many, or other)
|
|
46
|
+
*
|
|
47
|
+
* @example
|
|
48
|
+
* const pr = new Intl.PluralRules('en');
|
|
49
|
+
* pr.selectRange(1, 2); // "other" (English: "1-2 items")
|
|
50
|
+
* pr.selectRange(1, 1); // "one" (same value: "1 item")
|
|
51
|
+
*
|
|
52
|
+
* @example
|
|
53
|
+
* const prFr = new Intl.PluralRules('fr');
|
|
54
|
+
* prFr.selectRange(0, 1); // "one" (French: "0-1 vue")
|
|
55
|
+
* prFr.selectRange(1, 2); // "other" (French: "1-2 vues")
|
|
56
|
+
*
|
|
57
|
+
* @example
|
|
58
|
+
* // BigInt support (spec-compliant, but Chrome has a bug as of early 2025)
|
|
59
|
+
* pr.selectRange(BigInt(1), BigInt(2)); // "other"
|
|
60
|
+
*
|
|
61
|
+
* @throws {TypeError} If start or end is undefined
|
|
62
|
+
* @throws {RangeError} If start or end is not a finite number (Infinity, NaN)
|
|
63
|
+
*
|
|
64
|
+
* @note Chrome's native implementation (as of early 2025) has a bug where it throws
|
|
65
|
+
* "Cannot convert a BigInt value to a number" when using BigInt arguments. This is
|
|
66
|
+
* a browser bug - the spec requires BigInt support. This polyfill handles BigInt correctly.
|
|
67
|
+
*/
|
|
68
|
+
selectRange(start: number | bigint, end: number | bigint): LDMLPluralRule;
|
|
69
|
+
toString(): string;
|
|
70
|
+
static supportedLocalesOf(locales?: string | string[], options?: Pick<Intl.PluralRulesOptions, "localeMatcher">): string[];
|
|
71
|
+
static __addLocaleData(...data: PluralRulesLocaleData[]): void;
|
|
72
|
+
static localeData: Record<string, PluralRulesData>;
|
|
73
|
+
static availableLocales: Set<string>;
|
|
74
|
+
static __defaultLocale: string;
|
|
75
|
+
static getDefaultLocale(): string;
|
|
76
|
+
static relevantExtensionKeys: never[];
|
|
77
|
+
static polyfilled: boolean;
|
|
20
78
|
}
|