@formatjs/ecma402-abstract 1.11.6 → 1.11.9

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 (277) hide show
  1. package/262.ts +372 -0
  2. package/BUILD +97 -0
  3. package/CHANGELOG.md +709 -0
  4. package/CanonicalizeLocaleList.ts +8 -0
  5. package/CanonicalizeTimeZoneName.ts +29 -0
  6. package/CoerceOptionsToObject.ts +13 -0
  7. package/DefaultNumberOption.ts +28 -0
  8. package/GetNumberOption.ts +29 -0
  9. package/GetOption.ts +38 -0
  10. package/GetOptionsObject.ts +14 -0
  11. package/IsSanctionedSimpleUnitIdentifier.ts +66 -0
  12. package/IsValidTimeZoneName.ts +27 -0
  13. package/IsWellFormedCurrencyCode.ts +23 -0
  14. package/IsWellFormedUnitIdentifier.ts +32 -0
  15. package/LICENSE.md +0 -0
  16. package/NumberFormat/ComputeExponent.ts +49 -0
  17. package/NumberFormat/ComputeExponentForMagnitude.ts +71 -0
  18. package/NumberFormat/CurrencyDigits.ts +13 -0
  19. package/NumberFormat/FormatNumericToParts.ts +22 -0
  20. package/NumberFormat/FormatNumericToString.ts +71 -0
  21. package/NumberFormat/InitializeNumberFormat.ts +147 -0
  22. package/NumberFormat/PartitionNumberPattern.ts +86 -0
  23. package/NumberFormat/SetNumberFormatDigitOptions.ts +45 -0
  24. package/NumberFormat/SetNumberFormatUnitOptions.ts +77 -0
  25. package/NumberFormat/ToRawFixed.ts +56 -0
  26. package/NumberFormat/ToRawPrecision.ts +86 -0
  27. package/NumberFormat/digit-mapping.generated.ts +1 -0
  28. package/NumberFormat/format_to_parts.ts +571 -0
  29. package/PartitionPattern.ts +38 -0
  30. package/README.md +0 -0
  31. package/SupportedLocales.ts +31 -0
  32. package/data.ts +9 -0
  33. package/index.ts +45 -0
  34. package/package.json +3 -3
  35. package/regex.generated.ts +2 -0
  36. package/scripts/digit-mapping.ts +109 -0
  37. package/scripts/regex-gen.ts +19 -0
  38. package/tests/PartitionPattern.test.ts +38 -0
  39. package/tests/ToRawFixed.test.tsx +41 -0
  40. package/tests/ToRawPrecision.test.tsx +65 -0
  41. package/tsconfig.json +5 -0
  42. package/types/core.ts +11 -0
  43. package/types/date-time.ts +199 -0
  44. package/types/displaynames.ts +48 -0
  45. package/types/list.ts +22 -0
  46. package/types/number.ts +240 -0
  47. package/types/plural-rules.ts +18 -0
  48. package/types/relative-time.ts +74 -0
  49. package/utils.ts +128 -0
  50. package/262.d.ts +0 -92
  51. package/262.d.ts.map +0 -1
  52. package/262.js +0 -362
  53. package/CanonicalizeLocaleList.d.ts +0 -6
  54. package/CanonicalizeLocaleList.d.ts.map +0 -1
  55. package/CanonicalizeLocaleList.js +0 -12
  56. package/CanonicalizeTimeZoneName.d.ts +0 -9
  57. package/CanonicalizeTimeZoneName.d.ts.map +0 -1
  58. package/CanonicalizeTimeZoneName.js +0 -21
  59. package/CoerceOptionsToObject.d.ts +0 -7
  60. package/CoerceOptionsToObject.d.ts.map +0 -1
  61. package/CoerceOptionsToObject.js +0 -16
  62. package/DefaultNumberOption.d.ts +0 -9
  63. package/DefaultNumberOption.d.ts.map +0 -1
  64. package/DefaultNumberOption.js +0 -14
  65. package/GetNumberOption.d.ts +0 -10
  66. package/GetNumberOption.d.ts.map +0 -1
  67. package/GetNumberOption.js +0 -18
  68. package/GetOption.d.ts +0 -10
  69. package/GetOption.d.ts.map +0 -1
  70. package/GetOption.js +0 -35
  71. package/GetOptionsObject.d.ts +0 -7
  72. package/GetOptionsObject.d.ts.map +0 -1
  73. package/GetOptionsObject.js +0 -18
  74. package/IsSanctionedSimpleUnitIdentifier.d.ts +0 -14
  75. package/IsSanctionedSimpleUnitIdentifier.d.ts.map +0 -1
  76. package/IsSanctionedSimpleUnitIdentifier.js +0 -68
  77. package/IsValidTimeZoneName.d.ts +0 -10
  78. package/IsValidTimeZoneName.d.ts.map +0 -1
  79. package/IsValidTimeZoneName.js +0 -23
  80. package/IsWellFormedCurrencyCode.d.ts +0 -5
  81. package/IsWellFormedCurrencyCode.d.ts.map +0 -1
  82. package/IsWellFormedCurrencyCode.js +0 -25
  83. package/IsWellFormedUnitIdentifier.d.ts +0 -6
  84. package/IsWellFormedUnitIdentifier.d.ts.map +0 -1
  85. package/IsWellFormedUnitIdentifier.js +0 -32
  86. package/NumberFormat/ComputeExponent.d.ts +0 -12
  87. package/NumberFormat/ComputeExponent.d.ts.map +0 -1
  88. package/NumberFormat/ComputeExponent.js +0 -43
  89. package/NumberFormat/ComputeExponentForMagnitude.d.ts +0 -10
  90. package/NumberFormat/ComputeExponentForMagnitude.d.ts.map +0 -1
  91. package/NumberFormat/ComputeExponentForMagnitude.js +0 -64
  92. package/NumberFormat/CurrencyDigits.d.ts +0 -7
  93. package/NumberFormat/CurrencyDigits.d.ts.map +0 -1
  94. package/NumberFormat/CurrencyDigits.js +0 -14
  95. package/NumberFormat/FormatNumericToParts.d.ts +0 -5
  96. package/NumberFormat/FormatNumericToParts.d.ts.map +0 -1
  97. package/NumberFormat/FormatNumericToParts.js +0 -18
  98. package/NumberFormat/FormatNumericToString.d.ts +0 -9
  99. package/NumberFormat/FormatNumericToString.d.ts.map +0 -1
  100. package/NumberFormat/FormatNumericToString.js +0 -45
  101. package/NumberFormat/InitializeNumberFormat.d.ts +0 -13
  102. package/NumberFormat/InitializeNumberFormat.d.ts.map +0 -1
  103. package/NumberFormat/InitializeNumberFormat.js +0 -68
  104. package/NumberFormat/PartitionNumberPattern.d.ts +0 -8
  105. package/NumberFormat/PartitionNumberPattern.d.ts.map +0 -1
  106. package/NumberFormat/PartitionNumberPattern.js +0 -80
  107. package/NumberFormat/SetNumberFormatDigitOptions.d.ts +0 -6
  108. package/NumberFormat/SetNumberFormatDigitOptions.d.ts.map +0 -1
  109. package/NumberFormat/SetNumberFormatDigitOptions.js +0 -40
  110. package/NumberFormat/SetNumberFormatUnitOptions.d.ts +0 -8
  111. package/NumberFormat/SetNumberFormatUnitOptions.d.ts.map +0 -1
  112. package/NumberFormat/SetNumberFormatUnitOptions.js +0 -43
  113. package/NumberFormat/ToRawFixed.d.ts +0 -10
  114. package/NumberFormat/ToRawFixed.d.ts.map +0 -1
  115. package/NumberFormat/ToRawFixed.js +0 -55
  116. package/NumberFormat/ToRawPrecision.d.ts +0 -3
  117. package/NumberFormat/ToRawPrecision.d.ts.map +0 -1
  118. package/NumberFormat/ToRawPrecision.js +0 -78
  119. package/NumberFormat/digit-mapping.generated.d.ts +0 -2
  120. package/NumberFormat/digit-mapping.generated.d.ts.map +0 -1
  121. package/NumberFormat/digit-mapping.generated.js +0 -4
  122. package/NumberFormat/format_to_parts.d.ts +0 -22
  123. package/NumberFormat/format_to_parts.d.ts.map +0 -1
  124. package/NumberFormat/format_to_parts.js +0 -424
  125. package/PartitionPattern.d.ts +0 -9
  126. package/PartitionPattern.d.ts.map +0 -1
  127. package/PartitionPattern.js +0 -39
  128. package/SupportedLocales.d.ts +0 -10
  129. package/SupportedLocales.d.ts.map +0 -1
  130. package/SupportedLocales.js +0 -24
  131. package/data.d.ts +0 -6
  132. package/data.d.ts.map +0 -1
  133. package/data.js +0 -17
  134. package/index.d.ts +0 -37
  135. package/index.d.ts.map +0 -1
  136. package/index.js +0 -48
  137. package/lib/262.d.ts +0 -92
  138. package/lib/262.d.ts.map +0 -1
  139. package/lib/262.js +0 -336
  140. package/lib/CanonicalizeLocaleList.d.ts +0 -6
  141. package/lib/CanonicalizeLocaleList.d.ts.map +0 -1
  142. package/lib/CanonicalizeLocaleList.js +0 -8
  143. package/lib/CanonicalizeTimeZoneName.d.ts +0 -9
  144. package/lib/CanonicalizeTimeZoneName.d.ts.map +0 -1
  145. package/lib/CanonicalizeTimeZoneName.js +0 -17
  146. package/lib/CoerceOptionsToObject.d.ts +0 -7
  147. package/lib/CoerceOptionsToObject.d.ts.map +0 -1
  148. package/lib/CoerceOptionsToObject.js +0 -12
  149. package/lib/DefaultNumberOption.d.ts +0 -9
  150. package/lib/DefaultNumberOption.d.ts.map +0 -1
  151. package/lib/DefaultNumberOption.js +0 -10
  152. package/lib/GetNumberOption.d.ts +0 -10
  153. package/lib/GetNumberOption.d.ts.map +0 -1
  154. package/lib/GetNumberOption.js +0 -14
  155. package/lib/GetOption.d.ts +0 -10
  156. package/lib/GetOption.d.ts.map +0 -1
  157. package/lib/GetOption.js +0 -31
  158. package/lib/GetOptionsObject.d.ts +0 -7
  159. package/lib/GetOptionsObject.d.ts.map +0 -1
  160. package/lib/GetOptionsObject.js +0 -14
  161. package/lib/IsSanctionedSimpleUnitIdentifier.d.ts +0 -14
  162. package/lib/IsSanctionedSimpleUnitIdentifier.d.ts.map +0 -1
  163. package/lib/IsSanctionedSimpleUnitIdentifier.js +0 -63
  164. package/lib/IsValidTimeZoneName.d.ts +0 -10
  165. package/lib/IsValidTimeZoneName.d.ts.map +0 -1
  166. package/lib/IsValidTimeZoneName.js +0 -19
  167. package/lib/IsWellFormedCurrencyCode.d.ts +0 -5
  168. package/lib/IsWellFormedCurrencyCode.d.ts.map +0 -1
  169. package/lib/IsWellFormedCurrencyCode.js +0 -21
  170. package/lib/IsWellFormedUnitIdentifier.d.ts +0 -6
  171. package/lib/IsWellFormedUnitIdentifier.d.ts.map +0 -1
  172. package/lib/IsWellFormedUnitIdentifier.js +0 -28
  173. package/lib/NumberFormat/ComputeExponent.d.ts +0 -12
  174. package/lib/NumberFormat/ComputeExponent.d.ts.map +0 -1
  175. package/lib/NumberFormat/ComputeExponent.js +0 -39
  176. package/lib/NumberFormat/ComputeExponentForMagnitude.d.ts +0 -10
  177. package/lib/NumberFormat/ComputeExponentForMagnitude.d.ts.map +0 -1
  178. package/lib/NumberFormat/ComputeExponentForMagnitude.js +0 -60
  179. package/lib/NumberFormat/CurrencyDigits.d.ts +0 -7
  180. package/lib/NumberFormat/CurrencyDigits.d.ts.map +0 -1
  181. package/lib/NumberFormat/CurrencyDigits.js +0 -10
  182. package/lib/NumberFormat/FormatNumericToParts.d.ts +0 -5
  183. package/lib/NumberFormat/FormatNumericToParts.d.ts.map +0 -1
  184. package/lib/NumberFormat/FormatNumericToParts.js +0 -14
  185. package/lib/NumberFormat/FormatNumericToString.d.ts +0 -9
  186. package/lib/NumberFormat/FormatNumericToString.d.ts.map +0 -1
  187. package/lib/NumberFormat/FormatNumericToString.js +0 -41
  188. package/lib/NumberFormat/InitializeNumberFormat.d.ts +0 -13
  189. package/lib/NumberFormat/InitializeNumberFormat.d.ts.map +0 -1
  190. package/lib/NumberFormat/InitializeNumberFormat.js +0 -64
  191. package/lib/NumberFormat/PartitionNumberPattern.d.ts +0 -8
  192. package/lib/NumberFormat/PartitionNumberPattern.d.ts.map +0 -1
  193. package/lib/NumberFormat/PartitionNumberPattern.js +0 -75
  194. package/lib/NumberFormat/SetNumberFormatDigitOptions.d.ts +0 -6
  195. package/lib/NumberFormat/SetNumberFormatDigitOptions.d.ts.map +0 -1
  196. package/lib/NumberFormat/SetNumberFormatDigitOptions.js +0 -36
  197. package/lib/NumberFormat/SetNumberFormatUnitOptions.d.ts +0 -8
  198. package/lib/NumberFormat/SetNumberFormatUnitOptions.d.ts.map +0 -1
  199. package/lib/NumberFormat/SetNumberFormatUnitOptions.js +0 -39
  200. package/lib/NumberFormat/ToRawFixed.d.ts +0 -10
  201. package/lib/NumberFormat/ToRawFixed.d.ts.map +0 -1
  202. package/lib/NumberFormat/ToRawFixed.js +0 -51
  203. package/lib/NumberFormat/ToRawPrecision.d.ts +0 -3
  204. package/lib/NumberFormat/ToRawPrecision.d.ts.map +0 -1
  205. package/lib/NumberFormat/ToRawPrecision.js +0 -74
  206. package/lib/NumberFormat/digit-mapping.generated.d.ts +0 -2
  207. package/lib/NumberFormat/digit-mapping.generated.d.ts.map +0 -1
  208. package/lib/NumberFormat/digit-mapping.generated.js +0 -1
  209. package/lib/NumberFormat/format_to_parts.d.ts +0 -22
  210. package/lib/NumberFormat/format_to_parts.d.ts.map +0 -1
  211. package/lib/NumberFormat/format_to_parts.js +0 -421
  212. package/lib/PartitionPattern.d.ts +0 -9
  213. package/lib/PartitionPattern.d.ts.map +0 -1
  214. package/lib/PartitionPattern.js +0 -35
  215. package/lib/SupportedLocales.d.ts +0 -10
  216. package/lib/SupportedLocales.d.ts.map +0 -1
  217. package/lib/SupportedLocales.js +0 -20
  218. package/lib/data.d.ts +0 -6
  219. package/lib/data.d.ts.map +0 -1
  220. package/lib/data.js +0 -13
  221. package/lib/index.d.ts +0 -37
  222. package/lib/index.d.ts.map +0 -1
  223. package/lib/index.js +0 -34
  224. package/lib/regex.generated.d.ts +0 -2
  225. package/lib/regex.generated.d.ts.map +0 -1
  226. package/lib/regex.generated.js +0 -2
  227. package/lib/types/core.d.ts +0 -11
  228. package/lib/types/core.d.ts.map +0 -1
  229. package/lib/types/core.js +0 -1
  230. package/lib/types/date-time.d.ts +0 -135
  231. package/lib/types/date-time.d.ts.map +0 -1
  232. package/lib/types/date-time.js +0 -6
  233. package/lib/types/displaynames.d.ts +0 -47
  234. package/lib/types/displaynames.d.ts.map +0 -1
  235. package/lib/types/displaynames.js +0 -1
  236. package/lib/types/list.d.ts +0 -19
  237. package/lib/types/list.d.ts.map +0 -1
  238. package/lib/types/list.js +0 -1
  239. package/lib/types/number.d.ts +0 -147
  240. package/lib/types/number.d.ts.map +0 -1
  241. package/lib/types/number.js +0 -1
  242. package/lib/types/plural-rules.d.ts +0 -17
  243. package/lib/types/plural-rules.d.ts.map +0 -1
  244. package/lib/types/plural-rules.js +0 -1
  245. package/lib/types/relative-time.d.ts +0 -41
  246. package/lib/types/relative-time.d.ts.map +0 -1
  247. package/lib/types/relative-time.js +0 -1
  248. package/lib/utils.d.ts +0 -24
  249. package/lib/utils.d.ts.map +0 -1
  250. package/lib/utils.js +0 -78
  251. package/regex.generated.d.ts +0 -2
  252. package/regex.generated.d.ts.map +0 -1
  253. package/regex.generated.js +0 -5
  254. package/types/core.d.ts +0 -11
  255. package/types/core.d.ts.map +0 -1
  256. package/types/core.js +0 -2
  257. package/types/date-time.d.ts +0 -135
  258. package/types/date-time.d.ts.map +0 -1
  259. package/types/date-time.js +0 -9
  260. package/types/displaynames.d.ts +0 -47
  261. package/types/displaynames.d.ts.map +0 -1
  262. package/types/displaynames.js +0 -2
  263. package/types/list.d.ts +0 -19
  264. package/types/list.d.ts.map +0 -1
  265. package/types/list.js +0 -2
  266. package/types/number.d.ts +0 -147
  267. package/types/number.d.ts.map +0 -1
  268. package/types/number.js +0 -2
  269. package/types/plural-rules.d.ts +0 -17
  270. package/types/plural-rules.d.ts.map +0 -1
  271. package/types/plural-rules.js +0 -2
  272. package/types/relative-time.d.ts +0 -41
  273. package/types/relative-time.d.ts.map +0 -1
  274. package/types/relative-time.js +0 -2
  275. package/utils.d.ts +0 -24
  276. package/utils.d.ts.map +0 -1
  277. package/utils.js +0 -90
@@ -0,0 +1,571 @@
1
+ import {
2
+ NumberFormatOptionsStyle,
3
+ NumberFormatOptionsNotation,
4
+ NumberFormatOptionsCompactDisplay,
5
+ NumberFormatOptionsCurrencyDisplay,
6
+ NumberFormatOptionsCurrencySign,
7
+ NumberFormatOptionsUnitDisplay,
8
+ NumberFormatLocaleInternalData,
9
+ UnitData,
10
+ SymbolsData,
11
+ RawNumberFormatResult,
12
+ DecimalFormatNum,
13
+ LDMLPluralRuleMap,
14
+ NumberFormatPart,
15
+ } from '../types/number'
16
+ import {ToRawFixed} from './ToRawFixed'
17
+ import {LDMLPluralRule} from '../types/plural-rules'
18
+ import {digitMapping} from './digit-mapping.generated'
19
+ import {S_UNICODE_REGEX} from '../regex.generated'
20
+
21
+ // This is from: unicode-12.1.0/General_Category/Symbol/regex.js
22
+ // IE11 does not support unicode flag, otherwise this is just /\p{S}/u.
23
+ // /^\p{S}/u
24
+ const CARET_S_UNICODE_REGEX = new RegExp(`^${S_UNICODE_REGEX.source}`)
25
+ // /\p{S}$/u
26
+ const S_DOLLAR_UNICODE_REGEX = new RegExp(`${S_UNICODE_REGEX.source}$`)
27
+
28
+ const CLDR_NUMBER_PATTERN = /[#0](?:[\.,][#0]+)*/g
29
+
30
+ interface NumberResult {
31
+ formattedString: string
32
+ roundedNumber: number
33
+ sign: -1 | 0 | 1
34
+ // Example: 100K has exponent 3 and magnitude 5.
35
+ exponent: number
36
+ magnitude: number
37
+ }
38
+
39
+ export default function formatToParts(
40
+ numberResult: NumberResult,
41
+ data: NumberFormatLocaleInternalData,
42
+ pl: Intl.PluralRules,
43
+ options: {
44
+ numberingSystem: string
45
+ useGrouping: boolean
46
+ style: NumberFormatOptionsStyle
47
+ // Notation
48
+ notation: NumberFormatOptionsNotation
49
+ // Compact notation
50
+ compactDisplay?: NumberFormatOptionsCompactDisplay
51
+ // Currency
52
+ currency?: string
53
+ currencyDisplay?: NumberFormatOptionsCurrencyDisplay
54
+ currencySign?: NumberFormatOptionsCurrencySign
55
+ // Unit
56
+ unit?: string
57
+ unitDisplay?: NumberFormatOptionsUnitDisplay
58
+ }
59
+ ): NumberFormatPart[] {
60
+ const {sign, exponent, magnitude} = numberResult
61
+ const {notation, style, numberingSystem} = options
62
+ const defaultNumberingSystem = data.numbers.nu[0]
63
+
64
+ // #region Part 1: partition and interpolate the CLDR number pattern.
65
+ // ----------------------------------------------------------
66
+
67
+ let compactNumberPattern: string | null = null
68
+ if (notation === 'compact' && magnitude) {
69
+ compactNumberPattern = getCompactDisplayPattern(
70
+ numberResult,
71
+ pl,
72
+ data,
73
+ style,
74
+ options.compactDisplay!,
75
+ options.currencyDisplay,
76
+ numberingSystem
77
+ )
78
+ }
79
+
80
+ // This is used multiple times
81
+ let nonNameCurrencyPart: string | undefined
82
+ if (style === 'currency' && options.currencyDisplay !== 'name') {
83
+ const byCurrencyDisplay = data.currencies[options.currency!]
84
+ if (byCurrencyDisplay) {
85
+ switch (options.currencyDisplay!) {
86
+ case 'code':
87
+ nonNameCurrencyPart = options.currency!
88
+ break
89
+ case 'symbol':
90
+ nonNameCurrencyPart = byCurrencyDisplay.symbol
91
+ break
92
+ default:
93
+ nonNameCurrencyPart = byCurrencyDisplay.narrow
94
+ break
95
+ }
96
+ } else {
97
+ // Fallback for unknown currency
98
+ nonNameCurrencyPart = options.currency!
99
+ }
100
+ }
101
+
102
+ let numberPattern: string
103
+ if (!compactNumberPattern) {
104
+ // Note: if the style is unit, or is currency and the currency display is name,
105
+ // its unit parts will be interpolated in part 2. So here we can fallback to decimal.
106
+ if (
107
+ style === 'decimal' ||
108
+ style === 'unit' ||
109
+ (style === 'currency' && options.currencyDisplay === 'name')
110
+ ) {
111
+ // Shortcut for decimal
112
+ const decimalData =
113
+ data.numbers.decimal[numberingSystem] ||
114
+ data.numbers.decimal[defaultNumberingSystem]
115
+ numberPattern = getPatternForSign(decimalData.standard, sign)
116
+ } else if (style === 'currency') {
117
+ const currencyData =
118
+ data.numbers.currency[numberingSystem] ||
119
+ data.numbers.currency[defaultNumberingSystem]
120
+
121
+ // We replace number pattern part with `0` for easier postprocessing.
122
+ numberPattern = getPatternForSign(
123
+ currencyData[options.currencySign!],
124
+ sign
125
+ )
126
+ } else {
127
+ // percent
128
+ const percentPattern =
129
+ data.numbers.percent[numberingSystem] ||
130
+ data.numbers.percent[defaultNumberingSystem]
131
+ numberPattern = getPatternForSign(percentPattern, sign)
132
+ }
133
+ } else {
134
+ numberPattern = compactNumberPattern
135
+ }
136
+
137
+ // Extract the decimal number pattern string. It looks like "#,##0,00", which will later be
138
+ // used to infer decimal group sizes.
139
+ const decimalNumberPattern = CLDR_NUMBER_PATTERN.exec(numberPattern)![0]
140
+
141
+ // Now we start to substitute patterns
142
+ // 1. replace strings like `0` and `#,##0.00` with `{0}`
143
+ // 2. unquote characters (invariant: the quoted characters does not contain the special tokens)
144
+ numberPattern = numberPattern
145
+ .replace(CLDR_NUMBER_PATTERN, '{0}')
146
+ .replace(/'(.)'/g, '$1')
147
+
148
+ // Handle currency spacing (both compact and non-compact).
149
+ if (style === 'currency' && options.currencyDisplay !== 'name') {
150
+ const currencyData =
151
+ data.numbers.currency[numberingSystem] ||
152
+ data.numbers.currency[defaultNumberingSystem]
153
+ // See `currencySpacing` substitution rule in TR-35.
154
+ // Here we always assume the currencyMatch is "[:^S:]" and surroundingMatch is "[:digit:]".
155
+ //
156
+ // Example 1: for pattern "#,##0.00¤" with symbol "US$", we replace "¤" with the symbol,
157
+ // but insert an extra non-break space before the symbol, because "[:^S:]" matches "U" in
158
+ // "US$" and "[:digit:]" matches the latn numbering system digits.
159
+ //
160
+ // Example 2: for pattern "¤#,##0.00" with symbol "US$", there is no spacing between symbol
161
+ // and number, because `$` does not match "[:^S:]".
162
+ //
163
+ // Implementation note: here we do the best effort to infer the insertion.
164
+ // We also assume that `beforeInsertBetween` and `afterInsertBetween` will never be `;`.
165
+ const afterCurrency = currencyData.currencySpacing.afterInsertBetween
166
+ if (afterCurrency && !S_DOLLAR_UNICODE_REGEX.test(nonNameCurrencyPart!)) {
167
+ numberPattern = numberPattern.replace('¤{0}', `¤${afterCurrency}{0}`)
168
+ }
169
+ const beforeCurrency = currencyData.currencySpacing.beforeInsertBetween
170
+ if (beforeCurrency && !CARET_S_UNICODE_REGEX.test(nonNameCurrencyPart!)) {
171
+ numberPattern = numberPattern.replace('{0}¤', `{0}${beforeCurrency}¤`)
172
+ }
173
+ }
174
+
175
+ // The following tokens are special: `{0}`, `¤`, `%`, `-`, `+`, `{c:...}.
176
+ const numberPatternParts = numberPattern.split(/({c:[^}]+}|\{0\}|[¤%\-\+])/g)
177
+ const numberParts: NumberFormatPart[] = []
178
+
179
+ const symbols =
180
+ data.numbers.symbols[numberingSystem] ||
181
+ data.numbers.symbols[defaultNumberingSystem]
182
+
183
+ for (const part of numberPatternParts) {
184
+ if (!part) {
185
+ continue
186
+ }
187
+ switch (part) {
188
+ case '{0}': {
189
+ // We only need to handle scientific and engineering notation here.
190
+ numberParts.push(
191
+ ...paritionNumberIntoParts(
192
+ symbols,
193
+ numberResult,
194
+ notation,
195
+ exponent,
196
+ numberingSystem,
197
+ // If compact number pattern exists, do not insert group separators.
198
+ !compactNumberPattern && options.useGrouping,
199
+ decimalNumberPattern
200
+ )
201
+ )
202
+ break
203
+ }
204
+ case '-':
205
+ numberParts.push({type: 'minusSign', value: symbols.minusSign})
206
+ break
207
+ case '+':
208
+ numberParts.push({type: 'plusSign', value: symbols.plusSign})
209
+ break
210
+ case '%':
211
+ numberParts.push({type: 'percentSign', value: symbols.percentSign})
212
+ break
213
+ case '¤':
214
+ // Computed above when handling currency spacing.
215
+ numberParts.push({type: 'currency', value: nonNameCurrencyPart!})
216
+ break
217
+ default:
218
+ if (/^\{c:/.test(part)) {
219
+ numberParts.push({
220
+ type: 'compact',
221
+ value: part.substring(3, part.length - 1),
222
+ })
223
+ } else {
224
+ // literal
225
+ numberParts.push({type: 'literal', value: part})
226
+ }
227
+ break
228
+ }
229
+ }
230
+
231
+ // #endregion
232
+
233
+ // #region Part 2: interpolate unit pattern if necessary.
234
+ // ----------------------------------------------
235
+
236
+ switch (style) {
237
+ case 'currency': {
238
+ // `currencyDisplay: 'name'` has similar pattern handling as units.
239
+ if (options.currencyDisplay === 'name') {
240
+ const unitPattern = (
241
+ data.numbers.currency[numberingSystem] ||
242
+ data.numbers.currency[defaultNumberingSystem]
243
+ ).unitPattern
244
+
245
+ // Select plural
246
+ let unitName: string
247
+ const currencyNameData = data.currencies[options.currency!]
248
+ if (currencyNameData) {
249
+ unitName = selectPlural(
250
+ pl,
251
+ numberResult.roundedNumber * 10 ** exponent,
252
+ currencyNameData.displayName
253
+ )
254
+ } else {
255
+ // Fallback for unknown currency
256
+ unitName = options.currency!
257
+ }
258
+
259
+ // Do {0} and {1} substitution
260
+ const unitPatternParts = unitPattern.split(/(\{[01]\})/g)
261
+
262
+ const result: NumberFormatPart[] = []
263
+ for (const part of unitPatternParts) {
264
+ switch (part) {
265
+ case '{0}':
266
+ result.push(...numberParts)
267
+ break
268
+ case '{1}':
269
+ result.push({type: 'currency', value: unitName})
270
+ break
271
+ default:
272
+ if (part) {
273
+ result.push({type: 'literal', value: part})
274
+ }
275
+ break
276
+ }
277
+ }
278
+ return result
279
+ } else {
280
+ return numberParts
281
+ }
282
+ }
283
+ case 'unit': {
284
+ const {unit, unitDisplay} = options
285
+
286
+ let unitData: UnitData | undefined = data.units.simple[unit!]
287
+
288
+ let unitPattern: string
289
+ if (unitData) {
290
+ // Simple unit pattern
291
+ unitPattern = selectPlural(
292
+ pl,
293
+ numberResult.roundedNumber * 10 ** exponent,
294
+ data.units.simple[unit!][unitDisplay!]
295
+ )
296
+ } else {
297
+ // See: http://unicode.org/reports/tr35/tr35-general.html#perUnitPatterns
298
+ // If cannot find unit in the simple pattern, it must be "per" compound pattern.
299
+ // Implementation note: we are not following TR-35 here because we need to format to parts!
300
+ const [numeratorUnit, denominatorUnit] = unit!.split('-per-')
301
+ unitData = data.units.simple[numeratorUnit]
302
+
303
+ const numeratorUnitPattern = selectPlural(
304
+ pl,
305
+ numberResult.roundedNumber * 10 ** exponent,
306
+ data.units.simple[numeratorUnit!][unitDisplay!]
307
+ )
308
+ const perUnitPattern =
309
+ data.units.simple[denominatorUnit].perUnit[unitDisplay!]
310
+
311
+ if (perUnitPattern) {
312
+ // perUnitPattern exists, combine it with numeratorUnitPattern
313
+ unitPattern = perUnitPattern.replace('{0}', numeratorUnitPattern)
314
+ } else {
315
+ // get compoundUnit pattern (e.g. "{0} per {1}"), repalce {0} with numerator pattern and {1} with
316
+ // the denominator pattern in singular form.
317
+ const perPattern = data.units.compound.per[unitDisplay!]
318
+ const denominatorPattern = selectPlural(
319
+ pl,
320
+ 1,
321
+ data.units.simple[denominatorUnit][unitDisplay!]
322
+ )
323
+ unitPattern = unitPattern = perPattern
324
+ .replace('{0}', numeratorUnitPattern)
325
+ .replace('{1}', denominatorPattern.replace('{0}', ''))
326
+ }
327
+ }
328
+
329
+ const result: NumberFormatPart[] = []
330
+ // We need spacing around "{0}" because they are not treated as "unit" parts, but "literal".
331
+ for (const part of unitPattern.split(/(\s*\{0\}\s*)/)) {
332
+ const interpolateMatch = /^(\s*)\{0\}(\s*)$/.exec(part)
333
+ if (interpolateMatch) {
334
+ // Space before "{0}"
335
+ if (interpolateMatch[1]) {
336
+ result.push({type: 'literal', value: interpolateMatch[1]})
337
+ }
338
+ // "{0}" itself
339
+ result.push(...numberParts)
340
+ // Space after "{0}"
341
+ if (interpolateMatch[2]) {
342
+ result.push({type: 'literal', value: interpolateMatch[2]})
343
+ }
344
+ } else if (part) {
345
+ result.push({type: 'unit', value: part})
346
+ }
347
+ }
348
+
349
+ return result
350
+ }
351
+ default:
352
+ return numberParts
353
+ }
354
+
355
+ // #endregion
356
+ }
357
+
358
+ // A subset of https://tc39.es/ecma402/#sec-partitionnotationsubpattern
359
+ // Plus the exponent parts handling.
360
+ function paritionNumberIntoParts(
361
+ symbols: SymbolsData,
362
+ numberResult: Pick<
363
+ RawNumberFormatResult,
364
+ 'formattedString' | 'roundedNumber'
365
+ >,
366
+ notation: NumberFormatOptionsNotation,
367
+ exponent: number,
368
+ numberingSystem: string,
369
+ useGrouping: boolean,
370
+ /**
371
+ * This is the decimal number pattern without signs or symbols.
372
+ * It is used to infer the group size when `useGrouping` is true.
373
+ *
374
+ * A typical value looks like "#,##0.00" (primary group size is 3).
375
+ * Some locales like Hindi has secondary group size of 2 (e.g. "#,##,##0.00").
376
+ */
377
+ decimalNumberPattern: string
378
+ ): NumberFormatPart[] {
379
+ const result: NumberFormatPart[] = []
380
+ // eslint-disable-next-line prefer-const
381
+ let {formattedString: n, roundedNumber: x} = numberResult
382
+
383
+ if (isNaN(x)) {
384
+ return [{type: 'nan', value: n}]
385
+ } else if (!isFinite(x)) {
386
+ return [{type: 'infinity', value: n}]
387
+ }
388
+
389
+ const digitReplacementTable = digitMapping[numberingSystem as 'arab']
390
+ if (digitReplacementTable) {
391
+ n = n.replace(/\d/g, digit => digitReplacementTable[+digit] || digit)
392
+ }
393
+ // TODO: Else use an implementation dependent algorithm to map n to the appropriate
394
+ // representation of n in the given numbering system.
395
+ const decimalSepIndex = n.indexOf('.')
396
+ let integer: string
397
+ let fraction: string | undefined
398
+ if (decimalSepIndex > 0) {
399
+ integer = n.slice(0, decimalSepIndex)
400
+ fraction = n.slice(decimalSepIndex + 1)
401
+ } else {
402
+ integer = n
403
+ }
404
+
405
+ // #region Grouping integer digits
406
+
407
+ // The weird compact and x >= 10000 check is to ensure consistency with Node.js and Chrome.
408
+ // Note that `de` does not have compact form for thousands, but Node.js does not insert grouping separator
409
+ // unless the rounded number is greater than 10000:
410
+ // NumberFormat('de', {notation: 'compact', compactDisplay: 'short'}).format(1234) //=> "1234"
411
+ // NumberFormat('de').format(1234) //=> "1.234"
412
+ if (useGrouping && (notation !== 'compact' || x >= 10000)) {
413
+ const groupSepSymbol = symbols.group
414
+ const groups: string[] = []
415
+
416
+ // > There may be two different grouping sizes: The primary grouping size used for the least
417
+ // > significant integer group, and the secondary grouping size used for more significant groups.
418
+ // > If a pattern contains multiple grouping separators, the interval between the last one and the
419
+ // > end of the integer defines the primary grouping size, and the interval between the last two
420
+ // > defines the secondary grouping size. All others are ignored.
421
+ const integerNumberPattern = decimalNumberPattern.split('.')[0]
422
+ const patternGroups = integerNumberPattern.split(',')
423
+
424
+ let primaryGroupingSize = 3
425
+ let secondaryGroupingSize = 3
426
+
427
+ if (patternGroups.length > 1) {
428
+ primaryGroupingSize = patternGroups[patternGroups.length - 1].length
429
+ }
430
+ if (patternGroups.length > 2) {
431
+ secondaryGroupingSize = patternGroups[patternGroups.length - 2].length
432
+ }
433
+
434
+ let i = integer.length - primaryGroupingSize
435
+ if (i > 0) {
436
+ // Slice the least significant integer group
437
+ groups.push(integer.slice(i, i + primaryGroupingSize))
438
+ // Then iteratively push the more signicant groups
439
+ // TODO: handle surrogate pairs in some numbering system digits
440
+ for (i -= secondaryGroupingSize; i > 0; i -= secondaryGroupingSize) {
441
+ groups.push(integer.slice(i, i + secondaryGroupingSize))
442
+ }
443
+ groups.push(integer.slice(0, i + secondaryGroupingSize))
444
+ } else {
445
+ groups.push(integer)
446
+ }
447
+
448
+ while (groups.length > 0) {
449
+ const integerGroup = groups.pop()!
450
+ result.push({type: 'integer', value: integerGroup})
451
+ if (groups.length > 0) {
452
+ result.push({type: 'group', value: groupSepSymbol})
453
+ }
454
+ }
455
+ } else {
456
+ result.push({type: 'integer', value: integer})
457
+ }
458
+
459
+ // #endregion
460
+
461
+ if (fraction !== undefined) {
462
+ result.push(
463
+ {type: 'decimal', value: symbols.decimal},
464
+ {type: 'fraction', value: fraction}
465
+ )
466
+ }
467
+
468
+ if (
469
+ (notation === 'scientific' || notation === 'engineering') &&
470
+ isFinite(x)
471
+ ) {
472
+ result.push({type: 'exponentSeparator', value: symbols.exponential})
473
+ if (exponent < 0) {
474
+ result.push({type: 'exponentMinusSign', value: symbols.minusSign})
475
+ exponent = -exponent
476
+ }
477
+ const exponentResult = ToRawFixed(exponent, 0, 0)
478
+ result.push({
479
+ type: 'exponentInteger',
480
+ value: exponentResult.formattedString,
481
+ })
482
+ }
483
+
484
+ return result
485
+ }
486
+
487
+ function getPatternForSign(pattern: string, sign: -1 | 0 | 1): string {
488
+ if (pattern.indexOf(';') < 0) {
489
+ pattern = `${pattern};-${pattern}`
490
+ }
491
+ const [zeroPattern, negativePattern] = pattern.split(';')
492
+ switch (sign) {
493
+ case 0:
494
+ return zeroPattern
495
+ case -1:
496
+ return negativePattern
497
+ default:
498
+ return negativePattern.indexOf('-') >= 0
499
+ ? negativePattern.replace(/-/g, '+')
500
+ : `+${zeroPattern}`
501
+ }
502
+ }
503
+
504
+ // Find the CLDR pattern for compact notation based on the magnitude of data and style.
505
+ //
506
+ // Example return value: "¤ {c:laki}000;¤{c:laki} -0" (`sw` locale):
507
+ // - Notice the `{c:...}` token that wraps the compact literal.
508
+ // - The consecutive zeros are normalized to single zero to match CLDR_NUMBER_PATTERN.
509
+ //
510
+ // Returning null means the compact display pattern cannot be found.
511
+ function getCompactDisplayPattern(
512
+ numberResult: NumberResult,
513
+ pl: Intl.PluralRules,
514
+ data: NumberFormatLocaleInternalData,
515
+ style: NumberFormatOptionsStyle,
516
+ compactDisplay: NumberFormatOptionsCompactDisplay,
517
+ currencyDisplay: NumberFormatOptionsCurrencyDisplay | undefined,
518
+ numberingSystem: string
519
+ ): string | null {
520
+ const {roundedNumber, sign, magnitude} = numberResult
521
+ const magnitudeKey = String(10 ** magnitude) as DecimalFormatNum
522
+ const defaultNumberingSystem = data.numbers.nu[0]
523
+
524
+ let pattern: string
525
+ if (style === 'currency' && currencyDisplay !== 'name') {
526
+ const byNumberingSystem = data.numbers.currency
527
+ const currencyData =
528
+ byNumberingSystem[numberingSystem] ||
529
+ byNumberingSystem[defaultNumberingSystem]
530
+
531
+ // NOTE: compact notation ignores currencySign!
532
+ const compactPluralRules = currencyData.short?.[magnitudeKey]
533
+ if (!compactPluralRules) {
534
+ return null
535
+ }
536
+ pattern = selectPlural(pl, roundedNumber, compactPluralRules)
537
+ } else {
538
+ const byNumberingSystem = data.numbers.decimal
539
+ const byCompactDisplay =
540
+ byNumberingSystem[numberingSystem] ||
541
+ byNumberingSystem[defaultNumberingSystem]
542
+
543
+ const compactPlaralRule = byCompactDisplay[compactDisplay][magnitudeKey]
544
+ if (!compactPlaralRule) {
545
+ return null
546
+ }
547
+ pattern = selectPlural(pl, roundedNumber, compactPlaralRule)
548
+ }
549
+ // See https://unicode.org/reports/tr35/tr35-numbers.html#Compact_Number_Formats
550
+ // > If the value is precisely “0”, either explicit or defaulted, then the normal number format
551
+ // > pattern for that sort of object is supplied.
552
+ if (pattern === '0') {
553
+ return null
554
+ }
555
+
556
+ pattern = getPatternForSign(pattern, sign)
557
+ // Extract compact literal from the pattern
558
+ .replace(/([^\s;\-\+\d¤]+)/g, '{c:$1}')
559
+ // We replace one or more zeros with a single zero so it matches `CLDR_NUMBER_PATTERN`.
560
+ .replace(/0+/, '0')
561
+
562
+ return pattern
563
+ }
564
+
565
+ function selectPlural<T>(
566
+ pl: Intl.PluralRules,
567
+ x: number,
568
+ rules: LDMLPluralRuleMap<T>
569
+ ): T {
570
+ return rules[pl.select(x) as LDMLPluralRule] || rules.other
571
+ }
@@ -0,0 +1,38 @@
1
+ import {invariant} from './utils'
2
+
3
+ /**
4
+ * https://tc39.es/ecma402/#sec-partitionpattern
5
+ * @param pattern
6
+ */
7
+ export function PartitionPattern<T extends string>(
8
+ pattern: string
9
+ ): Array<{type: T; value: string | undefined}> {
10
+ const result = []
11
+ let beginIndex = pattern.indexOf('{')
12
+ let endIndex = 0
13
+ let nextIndex = 0
14
+ const length = pattern.length
15
+ while (beginIndex < pattern.length && beginIndex > -1) {
16
+ endIndex = pattern.indexOf('}', beginIndex)
17
+ invariant(endIndex > beginIndex, `Invalid pattern ${pattern}`)
18
+ if (beginIndex > nextIndex) {
19
+ result.push({
20
+ type: 'literal' as T,
21
+ value: pattern.substring(nextIndex, beginIndex),
22
+ })
23
+ }
24
+ result.push({
25
+ type: pattern.substring(beginIndex + 1, endIndex) as T,
26
+ value: undefined,
27
+ })
28
+ nextIndex = endIndex + 1
29
+ beginIndex = pattern.indexOf('{', nextIndex)
30
+ }
31
+ if (nextIndex < length) {
32
+ result.push({
33
+ type: 'literal' as T,
34
+ value: pattern.substring(nextIndex, length),
35
+ })
36
+ }
37
+ return result
38
+ }
package/README.md CHANGED
File without changes
@@ -0,0 +1,31 @@
1
+ import {ToObject} from './262'
2
+ import {GetOption} from './GetOption'
3
+ import {LookupSupportedLocales} from '@formatjs/intl-localematcher'
4
+
5
+ /**
6
+ * https://tc39.es/ecma402/#sec-supportedlocales
7
+ * @param availableLocales
8
+ * @param requestedLocales
9
+ * @param options
10
+ */
11
+ export function SupportedLocales(
12
+ availableLocales: Set<string>,
13
+ requestedLocales: string[],
14
+ options?: {localeMatcher?: 'best fit' | 'lookup'}
15
+ ): string[] {
16
+ let matcher: 'best fit' | 'lookup' = 'best fit'
17
+ if (options !== undefined) {
18
+ options = ToObject(options)
19
+ matcher = GetOption(
20
+ options,
21
+ 'localeMatcher',
22
+ 'string',
23
+ ['lookup', 'best fit'],
24
+ 'best fit'
25
+ ) as 'best fit'
26
+ }
27
+ if (matcher === 'best fit') {
28
+ return LookupSupportedLocales(availableLocales, requestedLocales)
29
+ }
30
+ return LookupSupportedLocales(availableLocales, requestedLocales)
31
+ }
package/data.ts ADDED
@@ -0,0 +1,9 @@
1
+ class MissingLocaleDataError extends Error {
2
+ public type = 'MISSING_LOCALE_DATA'
3
+ }
4
+
5
+ export function isMissingLocaleDataError(
6
+ e: Error
7
+ ): e is MissingLocaleDataError {
8
+ return (e as MissingLocaleDataError).type === 'MISSING_LOCALE_DATA'
9
+ }
package/index.ts ADDED
@@ -0,0 +1,45 @@
1
+ export * from './CanonicalizeLocaleList'
2
+ export * from './CanonicalizeTimeZoneName'
3
+ export * from './CoerceOptionsToObject'
4
+ export * from './GetNumberOption'
5
+ export * from './GetOption'
6
+ export * from './GetOptionsObject'
7
+ export * from './IsSanctionedSimpleUnitIdentifier'
8
+ export * from './IsValidTimeZoneName'
9
+ export * from './IsWellFormedCurrencyCode'
10
+ export * from './IsWellFormedUnitIdentifier'
11
+ export * from './NumberFormat/ComputeExponent'
12
+ export * from './NumberFormat/ComputeExponentForMagnitude'
13
+ export * from './NumberFormat/CurrencyDigits'
14
+ export * from './NumberFormat/FormatNumericToParts'
15
+ export * from './NumberFormat/FormatNumericToString'
16
+ export * from './NumberFormat/InitializeNumberFormat'
17
+ export * from './NumberFormat/PartitionNumberPattern'
18
+ export * from './NumberFormat/SetNumberFormatDigitOptions'
19
+ export * from './NumberFormat/SetNumberFormatUnitOptions'
20
+ export * from './NumberFormat/ToRawFixed'
21
+ export * from './NumberFormat/ToRawPrecision'
22
+ export {default as _formatToParts} from './NumberFormat/format_to_parts'
23
+ export * from './PartitionPattern'
24
+ export * from './SupportedLocales'
25
+ export {
26
+ getInternalSlot,
27
+ getMultiInternalSlots,
28
+ isLiteralPart,
29
+ setInternalSlot,
30
+ setMultiInternalSlots,
31
+ getMagnitude,
32
+ defineProperty,
33
+ } from './utils'
34
+ export type {LiteralPart} from './utils'
35
+
36
+ export {isMissingLocaleDataError} from './data'
37
+ export * from './types/relative-time'
38
+ export * from './types/date-time'
39
+ export * from './types/list'
40
+ export * from './types/plural-rules'
41
+ export * from './types/number'
42
+ export * from './types/displaynames'
43
+ export {invariant} from './utils'
44
+ export type {LocaleData} from './types/core'
45
+ export * from './262'