@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.
Files changed (233) hide show
  1. package/abstract/GetOperands.d.ts +56 -29
  2. package/abstract/GetOperands.js +48 -44
  3. package/abstract/InitializePluralRules.d.ts +7 -7
  4. package/abstract/InitializePluralRules.js +30 -16
  5. package/abstract/ResolvePlural.d.ts +41 -11
  6. package/abstract/ResolvePlural.js +73 -20
  7. package/abstract/ResolvePluralRange.d.ts +23 -0
  8. package/abstract/ResolvePluralRange.js +59 -0
  9. package/get_internal_slots.d.ts +2 -1
  10. package/get_internal_slots.js +8 -7
  11. package/index.d.ts +75 -17
  12. package/index.js +217 -137
  13. package/locale-data/af.js +14 -4
  14. package/locale-data/ak.js +14 -4
  15. package/locale-data/am.js +15 -4
  16. package/locale-data/an.js +14 -4
  17. package/locale-data/ar.js +22 -10
  18. package/locale-data/ars.js +21 -9
  19. package/locale-data/as.js +23 -8
  20. package/locale-data/asa.js +13 -3
  21. package/locale-data/ast.js +14 -4
  22. package/locale-data/az.js +21 -8
  23. package/locale-data/bal.js +15 -2
  24. package/locale-data/be.js +20 -8
  25. package/locale-data/bem.js +13 -3
  26. package/locale-data/bez.js +13 -3
  27. package/locale-data/bg.js +14 -4
  28. package/locale-data/bho.js +13 -3
  29. package/locale-data/bm.js +2 -2
  30. package/locale-data/bn.js +23 -8
  31. package/locale-data/bo.js +2 -2
  32. package/locale-data/br.js +19 -8
  33. package/locale-data/brx.js +13 -3
  34. package/locale-data/bs.js +18 -7
  35. package/locale-data/ca.js +25 -10
  36. package/locale-data/ce.js +13 -3
  37. package/locale-data/ceb.js +15 -4
  38. package/locale-data/cgg.js +13 -3
  39. package/locale-data/chr.js +13 -3
  40. package/locale-data/ckb.js +13 -3
  41. package/locale-data/cs.js +19 -8
  42. package/locale-data/cy.js +32 -14
  43. package/locale-data/da.js +17 -5
  44. package/locale-data/de.js +15 -5
  45. package/locale-data/doi.js +14 -3
  46. package/locale-data/dsb.js +19 -7
  47. package/locale-data/dv.js +13 -3
  48. package/locale-data/dz.js +2 -2
  49. package/locale-data/ee.js +13 -3
  50. package/locale-data/el.js +14 -4
  51. package/locale-data/en.js +22 -8
  52. package/locale-data/eo.js +13 -3
  53. package/locale-data/es.js +19 -7
  54. package/locale-data/et.js +15 -5
  55. package/locale-data/eu.js +14 -4
  56. package/locale-data/fa.js +15 -4
  57. package/locale-data/ff.js +13 -3
  58. package/locale-data/fi.js +15 -5
  59. package/locale-data/fil.js +19 -5
  60. package/locale-data/fo.js +13 -3
  61. package/locale-data/fr.js +21 -7
  62. package/locale-data/fur.js +13 -3
  63. package/locale-data/fy.js +14 -4
  64. package/locale-data/ga.js +22 -9
  65. package/locale-data/gd.js +23 -10
  66. package/locale-data/gl.js +15 -5
  67. package/locale-data/gsw.js +14 -4
  68. package/locale-data/gu.js +23 -8
  69. package/locale-data/guw.js +13 -3
  70. package/locale-data/gv.js +20 -8
  71. package/locale-data/ha.js +13 -3
  72. package/locale-data/haw.js +13 -3
  73. package/locale-data/he.js +17 -7
  74. package/locale-data/hi.js +23 -8
  75. package/locale-data/hnj.js +2 -2
  76. package/locale-data/hr.js +18 -7
  77. package/locale-data/hsb.js +19 -7
  78. package/locale-data/hu.js +16 -4
  79. package/locale-data/hy.js +17 -4
  80. package/locale-data/ia.js +15 -5
  81. package/locale-data/id.js +3 -3
  82. package/locale-data/ig.js +2 -2
  83. package/locale-data/ii.js +2 -2
  84. package/locale-data/io.js +15 -5
  85. package/locale-data/is.js +16 -5
  86. package/locale-data/it.js +21 -7
  87. package/locale-data/iu.js +15 -5
  88. package/locale-data/ja.js +3 -3
  89. package/locale-data/jbo.js +2 -2
  90. package/locale-data/jgo.js +13 -3
  91. package/locale-data/jmc.js +13 -3
  92. package/locale-data/jv.js +2 -2
  93. package/locale-data/jw.js +2 -2
  94. package/locale-data/ka.js +19 -7
  95. package/locale-data/kab.js +13 -3
  96. package/locale-data/kaj.js +13 -3
  97. package/locale-data/kcg.js +13 -3
  98. package/locale-data/kde.js +2 -2
  99. package/locale-data/kea.js +2 -2
  100. package/locale-data/kk.js +16 -5
  101. package/locale-data/kkj.js +13 -3
  102. package/locale-data/kl.js +13 -3
  103. package/locale-data/km.js +3 -3
  104. package/locale-data/kn.js +15 -4
  105. package/locale-data/ko.js +3 -3
  106. package/locale-data/ks.js +13 -3
  107. package/locale-data/ksb.js +13 -3
  108. package/locale-data/ksh.js +15 -5
  109. package/locale-data/ku.js +13 -3
  110. package/locale-data/kw.js +25 -11
  111. package/locale-data/ky.js +14 -4
  112. package/locale-data/lag.js +16 -6
  113. package/locale-data/lb.js +13 -3
  114. package/locale-data/lg.js +13 -3
  115. package/locale-data/lij.js +18 -5
  116. package/locale-data/lkt.js +2 -2
  117. package/locale-data/ln.js +13 -3
  118. package/locale-data/lo.js +14 -4
  119. package/locale-data/lt.js +20 -8
  120. package/locale-data/lv.js +18 -7
  121. package/locale-data/mas.js +13 -3
  122. package/locale-data/mg.js +13 -3
  123. package/locale-data/mgo.js +13 -3
  124. package/locale-data/mk.js +22 -8
  125. package/locale-data/ml.js +14 -4
  126. package/locale-data/mn.js +14 -4
  127. package/locale-data/mo.js +19 -6
  128. package/locale-data/mr.js +20 -7
  129. package/locale-data/ms.js +14 -4
  130. package/locale-data/mt.js +19 -8
  131. package/locale-data/my.js +3 -3
  132. package/locale-data/nah.js +13 -3
  133. package/locale-data/naq.js +15 -5
  134. package/locale-data/nb.js +14 -4
  135. package/locale-data/nd.js +13 -3
  136. package/locale-data/ne.js +16 -5
  137. package/locale-data/nl.js +15 -5
  138. package/locale-data/nn.js +13 -3
  139. package/locale-data/nnh.js +13 -3
  140. package/locale-data/no.js +14 -4
  141. package/locale-data/nqo.js +2 -2
  142. package/locale-data/nr.js +13 -3
  143. package/locale-data/nso.js +13 -3
  144. package/locale-data/ny.js +13 -3
  145. package/locale-data/nyn.js +13 -3
  146. package/locale-data/om.js +13 -3
  147. package/locale-data/or.js +22 -9
  148. package/locale-data/os.js +13 -3
  149. package/locale-data/osa.js +2 -2
  150. package/locale-data/pa.js +14 -4
  151. package/locale-data/pap.js +13 -3
  152. package/locale-data/pcm.js +15 -4
  153. package/locale-data/pl.js +19 -8
  154. package/locale-data/prg.js +17 -6
  155. package/locale-data/ps.js +14 -4
  156. package/locale-data/pt-PT.js +17 -6
  157. package/locale-data/pt.js +18 -7
  158. package/locale-data/rm.js +13 -3
  159. package/locale-data/ro.js +20 -7
  160. package/locale-data/rof.js +13 -3
  161. package/locale-data/ru.js +19 -8
  162. package/locale-data/rwk.js +13 -3
  163. package/locale-data/sah.js +2 -2
  164. package/locale-data/saq.js +13 -3
  165. package/locale-data/sat.js +15 -5
  166. package/locale-data/sc.js +18 -5
  167. package/locale-data/scn.js +21 -7
  168. package/locale-data/sd.js +14 -4
  169. package/locale-data/sdh.js +13 -3
  170. package/locale-data/se.js +15 -5
  171. package/locale-data/seh.js +13 -3
  172. package/locale-data/ses.js +2 -2
  173. package/locale-data/sg.js +2 -2
  174. package/locale-data/sh.js +17 -6
  175. package/locale-data/shi.js +16 -6
  176. package/locale-data/si.js +17 -5
  177. package/locale-data/sk.js +19 -8
  178. package/locale-data/sl.js +19 -8
  179. package/locale-data/sma.js +15 -5
  180. package/locale-data/smi.js +15 -5
  181. package/locale-data/smj.js +15 -5
  182. package/locale-data/smn.js +15 -5
  183. package/locale-data/sms.js +15 -5
  184. package/locale-data/sn.js +13 -3
  185. package/locale-data/so.js +13 -3
  186. package/locale-data/sq.js +18 -7
  187. package/locale-data/sr.js +18 -7
  188. package/locale-data/ss.js +13 -3
  189. package/locale-data/ssy.js +13 -3
  190. package/locale-data/st.js +13 -3
  191. package/locale-data/su.js +2 -2
  192. package/locale-data/sv.js +18 -5
  193. package/locale-data/sw.js +15 -5
  194. package/locale-data/syr.js +13 -3
  195. package/locale-data/ta.js +14 -4
  196. package/locale-data/te.js +14 -4
  197. package/locale-data/teo.js +13 -3
  198. package/locale-data/th.js +3 -3
  199. package/locale-data/ti.js +13 -3
  200. package/locale-data/tig.js +13 -3
  201. package/locale-data/tk.js +16 -5
  202. package/locale-data/tl.js +18 -4
  203. package/locale-data/tn.js +13 -3
  204. package/locale-data/to.js +2 -2
  205. package/locale-data/tpi.js +2 -2
  206. package/locale-data/tr.js +14 -4
  207. package/locale-data/ts.js +13 -3
  208. package/locale-data/tzm.js +13 -4
  209. package/locale-data/ug.js +14 -4
  210. package/locale-data/uk.js +22 -8
  211. package/locale-data/und.js +2 -2
  212. package/locale-data/ur.js +15 -5
  213. package/locale-data/uz.js +14 -4
  214. package/locale-data/ve.js +13 -3
  215. package/locale-data/vi.js +14 -4
  216. package/locale-data/vo.js +13 -3
  217. package/locale-data/vun.js +13 -3
  218. package/locale-data/wa.js +13 -3
  219. package/locale-data/wae.js +13 -3
  220. package/locale-data/wo.js +2 -2
  221. package/locale-data/xh.js +13 -3
  222. package/locale-data/xog.js +13 -3
  223. package/locale-data/yi.js +14 -4
  224. package/locale-data/yo.js +2 -2
  225. package/locale-data/yue.js +3 -3
  226. package/locale-data/zh.js +3 -3
  227. package/locale-data/zu.js +15 -4
  228. package/package.json +7 -7
  229. package/polyfill-force.js +6 -6
  230. package/polyfill.iife.js +2599 -3121
  231. package/polyfill.js +8 -8
  232. package/should-polyfill.js +11 -15
  233. package/supported-locales.generated.js +216 -216
@@ -1,32 +1,59 @@
1
- import Decimal from 'decimal.js';
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
- * Absolute value of the source number (integer and decimals)
5
- */
6
- Number: Decimal;
7
- /**
8
- * Number of digits of `number`
9
- */
10
- IntegerDigits: number;
11
- /**
12
- * Number of visible fraction digits in [[Number]], with trailing zeroes.
13
- */
14
- NumberOfFractionDigits: number;
15
- /**
16
- * Number of visible fraction digits in [[Number]], without trailing zeroes.
17
- */
18
- NumberOfFractionDigitsWithoutTrailing: number;
19
- /**
20
- * Number of visible fractional digits in [[Number]], with trailing zeroes.
21
- */
22
- FractionDigits: number;
23
- /**
24
- * Number of visible fractional digits in [[Number]], without trailing zeroes.
25
- */
26
- FractionDigitsWithoutTrailing: number;
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
- * http://ecma-international.org/ecma-402/7.0/index.html#sec-getoperands
30
- * @param s
31
- */
32
- export declare function GetOperands(s: string): OperandsRecord;
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;
@@ -1,46 +1,50 @@
1
- import { invariant, ToNumber, ZERO } from '@formatjs/ecma402-abstract';
1
+ import { invariant, ToNumber, ZERO } from "@formatjs/ecma402-abstract";
2
2
  /**
3
- * http://ecma-international.org/ecma-402/7.0/index.html#sec-getoperands
4
- * @param s
5
- */
6
- export function GetOperands(s) {
7
- invariant(typeof s === 'string', "GetOperands should have been called with a string");
8
- var n = ToNumber(s);
9
- invariant(n.isFinite(), 'n should be finite');
10
- var dp = s.indexOf('.');
11
- var iv;
12
- var f;
13
- var v;
14
- var fv = '';
15
- if (dp === -1) {
16
- iv = n;
17
- f = ZERO;
18
- v = 0;
19
- }
20
- else {
21
- iv = s.slice(0, dp);
22
- fv = s.slice(dp, s.length);
23
- f = ToNumber(fv);
24
- v = fv.length;
25
- }
26
- var i = ToNumber(iv).abs();
27
- var w;
28
- var t;
29
- if (!f.isZero()) {
30
- var ft = fv.replace(/0+$/, '');
31
- w = ft.length;
32
- t = ToNumber(ft);
33
- }
34
- else {
35
- w = 0;
36
- t = ZERO;
37
- }
38
- return {
39
- Number: n,
40
- IntegerDigits: i.toNumber(),
41
- NumberOfFractionDigits: v,
42
- NumberOfFractionDigitsWithoutTrailing: w,
43
- FractionDigits: f.toNumber(),
44
- FractionDigitsWithoutTrailing: t.toNumber(),
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 '@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;
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, } from '@formatjs/ecma402-abstract';
2
- import { ResolveLocale } from '@formatjs/intl-localematcher';
3
- export function InitializePluralRules(pl, locales, options, _a) {
4
- var availableLocales = _a.availableLocales, relevantExtensionKeys = _a.relevantExtensionKeys, localeData = _a.localeData, getDefaultLocale = _a.getDefaultLocale, getInternalSlots = _a.getInternalSlots;
5
- var requestedLocales = CanonicalizeLocaleList(locales);
6
- var opt = Object.create(null);
7
- var opts = CoerceOptionsToObject(options);
8
- var internalSlots = getInternalSlots(pl);
9
- internalSlots.initializedPluralRules = true;
10
- var matcher = GetOption(opts, 'localeMatcher', 'string', ['best fit', 'lookup'], 'best fit');
11
- opt.localeMatcher = matcher;
12
- var r = ResolveLocale(availableLocales, requestedLocales, opt, relevantExtensionKeys, localeData, getDefaultLocale);
13
- internalSlots.locale = r.locale;
14
- internalSlots.type = GetOption(opts, 'type', 'string', ['cardinal', 'ordinal'], 'cardinal');
15
- SetNumberFormatDigitOptions(internalSlots, opts, 0, 3, 'standard');
16
- return pl;
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 '@formatjs/ecma402-abstract';
2
- import Decimal from 'decimal.js';
3
- import { OperandsRecord } from './GetOperands.js';
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
- * http://ecma-international.org/ecma-402/7.0/index.html#sec-resolveplural
6
- * @param pl
7
- * @param n
8
- * @param PluralRuleSelect Has to pass in bc it's implementation-specific
9
- */
10
- export declare function ResolvePlural(pl: Intl.PluralRules, n: Decimal, { getInternalSlots, PluralRuleSelect, }: {
11
- getInternalSlots(pl: Intl.PluralRules): PluralRulesInternal;
12
- PluralRuleSelect: (locale: string, type: 'cardinal' | 'ordinal', n: Decimal, operands: OperandsRecord) => LDMLPluralRule;
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, } from '@formatjs/ecma402-abstract';
2
- import { GetOperands } from './GetOperands.js';
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
- * http://ecma-international.org/ecma-402/7.0/index.html#sec-resolveplural
5
- * @param pl
6
- * @param n
7
- * @param PluralRuleSelect Has to pass in bc it's implementation-specific
8
- */
9
- export function ResolvePlural(pl, n, _a) {
10
- var getInternalSlots = _a.getInternalSlots, PluralRuleSelect = _a.PluralRuleSelect;
11
- var internalSlots = getInternalSlots(pl);
12
- invariant(Type(internalSlots) === 'Object', 'pl has to be an object');
13
- invariant('initializedPluralRules' in internalSlots, 'pluralrules must be initialized');
14
- if (!n.isFinite()) {
15
- return 'other';
16
- }
17
- var locale = internalSlots.locale, type = internalSlots.type;
18
- var res = FormatNumericToString(internalSlots, n);
19
- var s = res.formattedString;
20
- var operands = GetOperands(s);
21
- return PluralRuleSelect(locale, type, n, operands);
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
+ }
@@ -1,2 +1,3 @@
1
- import { PluralRules, PluralRulesInternal } from '.';
1
+ import type { PluralRules } from "./index.js";
2
+ import { type PluralRulesInternal } from "./index.js";
2
3
  export default function getInternalSlots(x: PluralRules): PluralRulesInternal;
@@ -1,9 +1,10 @@
1
- var internalSlotMap = new WeakMap();
1
+ import "./index.js";
2
+ const internalSlotMap = new WeakMap();
2
3
  export default function getInternalSlots(x) {
3
- var internalSlots = internalSlotMap.get(x);
4
- if (!internalSlots) {
5
- internalSlots = Object.create(null);
6
- internalSlotMap.set(x, internalSlots);
7
- }
8
- return internalSlots;
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 '@formatjs/ecma402-abstract';
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
- initializedPluralRules: boolean;
4
- locale: string;
5
- type: 'cardinal' | 'ordinal';
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 implements Intl.PluralRules {
8
- constructor(locales?: string | string[], options?: Intl.PluralRulesOptions);
9
- resolvedOptions(): Intl.ResolvedPluralRulesOptions;
10
- select(val: number): LDMLPluralRule;
11
- toString(): string;
12
- static supportedLocalesOf(locales?: string | string[], options?: Pick<Intl.PluralRulesOptions, 'localeMatcher'>): string[];
13
- static __addLocaleData(...data: PluralRulesLocaleData[]): void;
14
- static localeData: Record<string, PluralRulesData>;
15
- static availableLocales: Set<string>;
16
- static __defaultLocale: string;
17
- static getDefaultLocale(): string;
18
- static relevantExtensionKeys: never[];
19
- static polyfilled: boolean;
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
  }