@stridge/noctis-intl 1.0.0-beta.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 (82) hide show
  1. package/LICENSE +201 -0
  2. package/README.md +41 -0
  3. package/dist/date/CalendarDate.d.ts +211 -0
  4. package/dist/date/CalendarDate.js +340 -0
  5. package/dist/date/DateFormatter.d.ts +26 -0
  6. package/dist/date/DateFormatter.js +114 -0
  7. package/dist/date/calendars/BuddhistCalendar.d.ts +20 -0
  8. package/dist/date/calendars/BuddhistCalendar.js +33 -0
  9. package/dist/date/calendars/EthiopicCalendar.d.ts +49 -0
  10. package/dist/date/calendars/EthiopicCalendar.js +129 -0
  11. package/dist/date/calendars/GregorianCalendar.d.ts +26 -0
  12. package/dist/date/calendars/GregorianCalendar.js +117 -0
  13. package/dist/date/calendars/HebrewCalendar.d.ts +25 -0
  14. package/dist/date/calendars/HebrewCalendar.js +114 -0
  15. package/dist/date/calendars/IndianCalendar.d.ts +21 -0
  16. package/dist/date/calendars/IndianCalendar.js +75 -0
  17. package/dist/date/calendars/IslamicCalendar.d.ts +55 -0
  18. package/dist/date/calendars/IslamicCalendar.js +162 -0
  19. package/dist/date/calendars/JapaneseCalendar.d.ts +26 -0
  20. package/dist/date/calendars/JapaneseCalendar.js +154 -0
  21. package/dist/date/calendars/PersianCalendar.d.ts +23 -0
  22. package/dist/date/calendars/PersianCalendar.js +63 -0
  23. package/dist/date/calendars/TaiwanCalendar.d.ts +23 -0
  24. package/dist/date/calendars/TaiwanCalendar.js +51 -0
  25. package/dist/date/conversion.d.ts +41 -0
  26. package/dist/date/conversion.js +181 -0
  27. package/dist/date/createCalendar.d.ts +7 -0
  28. package/dist/date/createCalendar.js +30 -0
  29. package/dist/date/manipulation.js +274 -0
  30. package/dist/date/queries.d.ts +92 -0
  31. package/dist/date/queries.js +233 -0
  32. package/dist/date/string.d.ts +36 -0
  33. package/dist/date/string.js +162 -0
  34. package/dist/date/types.d.ts +138 -0
  35. package/dist/date/utils.d.ts +4 -0
  36. package/dist/date/utils.js +6 -0
  37. package/dist/date/weekStartData.js +100 -0
  38. package/dist/date.d.ts +17 -0
  39. package/dist/date.js +16 -0
  40. package/dist/i18n/context.d.ts +17 -0
  41. package/dist/i18n/context.js +13 -0
  42. package/dist/i18n/use-collator.d.ts +8 -0
  43. package/dist/i18n/use-collator.js +19 -0
  44. package/dist/i18n/use-date-formatter.d.ts +14 -0
  45. package/dist/i18n/use-date-formatter.js +25 -0
  46. package/dist/i18n/use-deep-memo.d.ts +8 -0
  47. package/dist/i18n/use-deep-memo.js +15 -0
  48. package/dist/i18n/use-filter.d.ts +17 -0
  49. package/dist/i18n/use-filter.js +49 -0
  50. package/dist/i18n/use-list-formatter.d.ts +5 -0
  51. package/dist/i18n/use-list-formatter.js +11 -0
  52. package/dist/i18n/use-locale.d.ts +7 -0
  53. package/dist/i18n/use-locale.js +13 -0
  54. package/dist/i18n/use-localized-string-formatter.d.ts +13 -0
  55. package/dist/i18n/use-localized-string-formatter.js +30 -0
  56. package/dist/i18n/use-number-formatter.d.ts +14 -0
  57. package/dist/i18n/use-number-formatter.js +15 -0
  58. package/dist/i18n/utils.d.ts +15 -0
  59. package/dist/i18n/utils.js +50 -0
  60. package/dist/index.d.ts +22 -0
  61. package/dist/index.js +21 -0
  62. package/dist/messages/en.d.ts +6 -0
  63. package/dist/messages/en.js +68 -0
  64. package/dist/messages/fa.d.ts +6 -0
  65. package/dist/messages/fa.js +68 -0
  66. package/dist/number/NumberFormatter.d.ts +32 -0
  67. package/dist/number/NumberFormatter.js +135 -0
  68. package/dist/number/NumberParser.d.ts +30 -0
  69. package/dist/number/NumberParser.js +234 -0
  70. package/dist/number.d.ts +3 -0
  71. package/dist/number.js +3 -0
  72. package/dist/react.d.ts +17 -0
  73. package/dist/react.js +17 -0
  74. package/dist/ssr/use-is-ssr.d.ts +8 -0
  75. package/dist/ssr/use-is-ssr.js +15 -0
  76. package/dist/string/LocalizedStringDictionary.d.ts +22 -0
  77. package/dist/string/LocalizedStringDictionary.js +58 -0
  78. package/dist/string/LocalizedStringFormatter.d.ts +22 -0
  79. package/dist/string/LocalizedStringFormatter.js +46 -0
  80. package/dist/string.d.ts +3 -0
  81. package/dist/string.js +3 -0
  82. package/package.json +80 -0
@@ -0,0 +1,32 @@
1
+ //#region src/number/NumberFormatter.d.ts
2
+ interface NumberFormatOptions extends Intl.NumberFormatOptions {
3
+ /** Overrides default numbering system for the current locale. */
4
+ numberingSystem?: string;
5
+ }
6
+ interface NumberRangeFormatPart extends Intl.NumberFormatPart {
7
+ source: "startRange" | "endRange" | "shared";
8
+ }
9
+ /**
10
+ * A wrapper around Intl.NumberFormat providing additional options, polyfills, and caching for
11
+ * performance.
12
+ */
13
+ declare class NumberFormatter implements Intl.NumberFormat {
14
+ private numberFormatter;
15
+ private options;
16
+ constructor(locale: string, options?: NumberFormatOptions);
17
+ /**
18
+ * Formats a number value as a string, according to the locale and options provided to the
19
+ * constructor.
20
+ */
21
+ format(value: number): string;
22
+ /** Formats a number to an array of parts such as separators, digits, punctuation, and more. */
23
+ formatToParts(value: number): Intl.NumberFormatPart[];
24
+ /** Formats a number range as a string. */
25
+ formatRange(start: number, end: number): string;
26
+ /** Formats a number range as an array of parts. */
27
+ formatRangeToParts(start: number, end: number): NumberRangeFormatPart[];
28
+ /** Returns the resolved formatting options based on the values passed to the constructor. */
29
+ resolvedOptions(): Intl.ResolvedNumberFormatOptions;
30
+ }
31
+ //#endregion
32
+ export { NumberFormatOptions, NumberFormatter };
@@ -0,0 +1,135 @@
1
+ //#region src/number/NumberFormatter.ts
2
+ let formatterCache = /* @__PURE__ */ new Map();
3
+ let supportsSignDisplay = false;
4
+ try {
5
+ supportsSignDisplay = new Intl.NumberFormat("de-DE", { signDisplay: "exceptZero" }).resolvedOptions().signDisplay === "exceptZero";
6
+ } catch {}
7
+ let supportsUnit = false;
8
+ try {
9
+ supportsUnit = new Intl.NumberFormat("de-DE", {
10
+ style: "unit",
11
+ unit: "degree"
12
+ }).resolvedOptions().style === "unit";
13
+ } catch {}
14
+ const UNITS = { degree: { narrow: {
15
+ default: "°",
16
+ "ja-JP": " 度",
17
+ "zh-TW": "度",
18
+ "sl-SI": " °"
19
+ } } };
20
+ /**
21
+ * A wrapper around Intl.NumberFormat providing additional options, polyfills, and caching for
22
+ * performance.
23
+ */
24
+ var NumberFormatter = class {
25
+ numberFormatter;
26
+ options;
27
+ constructor(locale, options = {}) {
28
+ this.numberFormatter = getCachedNumberFormatter(locale, options);
29
+ this.options = options;
30
+ }
31
+ /**
32
+ * Formats a number value as a string, according to the locale and options provided to the
33
+ * constructor.
34
+ */
35
+ format(value) {
36
+ let res = "";
37
+ if (!supportsSignDisplay && this.options.signDisplay != null) res = numberFormatSignDisplayPolyfill(this.numberFormatter, this.options.signDisplay, value);
38
+ else res = this.numberFormatter.format(value);
39
+ if (this.options.style === "unit" && !supportsUnit) {
40
+ let { unit, unitDisplay = "short", locale } = this.resolvedOptions();
41
+ if (!unit) return res;
42
+ let values = UNITS[unit]?.[unitDisplay];
43
+ res += values[locale] || values.default;
44
+ }
45
+ return res;
46
+ }
47
+ /** Formats a number to an array of parts such as separators, digits, punctuation, and more. */
48
+ formatToParts(value) {
49
+ return this.numberFormatter.formatToParts(value);
50
+ }
51
+ /** Formats a number range as a string. */
52
+ formatRange(start, end) {
53
+ if (typeof this.numberFormatter.formatRange === "function") return this.numberFormatter.formatRange(start, end);
54
+ if (end < start) throw new RangeError("End date must be >= start date");
55
+ return `${this.format(start)} – ${this.format(end)}`;
56
+ }
57
+ /** Formats a number range as an array of parts. */
58
+ formatRangeToParts(start, end) {
59
+ if (typeof this.numberFormatter.formatRangeToParts === "function") return this.numberFormatter.formatRangeToParts(start, end);
60
+ if (end < start) throw new RangeError("End date must be >= start date");
61
+ let startParts = this.numberFormatter.formatToParts(start);
62
+ let endParts = this.numberFormatter.formatToParts(end);
63
+ return [
64
+ ...startParts.map((p) => ({
65
+ ...p,
66
+ source: "startRange"
67
+ })),
68
+ {
69
+ type: "literal",
70
+ value: " – ",
71
+ source: "shared"
72
+ },
73
+ ...endParts.map((p) => ({
74
+ ...p,
75
+ source: "endRange"
76
+ }))
77
+ ];
78
+ }
79
+ /** Returns the resolved formatting options based on the values passed to the constructor. */
80
+ resolvedOptions() {
81
+ let options = this.numberFormatter.resolvedOptions();
82
+ if (!supportsSignDisplay && this.options.signDisplay != null) options = {
83
+ ...options,
84
+ signDisplay: this.options.signDisplay
85
+ };
86
+ if (!supportsUnit && this.options.style === "unit") options = {
87
+ ...options,
88
+ style: "unit",
89
+ unit: this.options.unit,
90
+ unitDisplay: this.options.unitDisplay
91
+ };
92
+ return options;
93
+ }
94
+ };
95
+ function getCachedNumberFormatter(locale, options = {}) {
96
+ let { numberingSystem } = options;
97
+ if (numberingSystem && locale.includes("-nu-")) {
98
+ if (!locale.includes("-u-")) locale += "-u-";
99
+ locale += `-nu-${numberingSystem}`;
100
+ }
101
+ if (options.style === "unit" && !supportsUnit) {
102
+ let { unit, unitDisplay = "short" } = options;
103
+ if (!unit) throw new Error("unit option must be provided with style: \"unit\"");
104
+ if (!UNITS[unit]?.[unitDisplay]) throw new Error(`Unsupported unit ${unit} with unitDisplay = ${unitDisplay}`);
105
+ options = {
106
+ ...options,
107
+ style: "decimal"
108
+ };
109
+ }
110
+ let cacheKey = locale + (options ? Object.entries(options).sort((a, b) => a[0] < b[0] ? -1 : 1).join() : "");
111
+ if (formatterCache.has(cacheKey)) return formatterCache.get(cacheKey);
112
+ let numberFormatter = new Intl.NumberFormat(locale, options);
113
+ formatterCache.set(cacheKey, numberFormatter);
114
+ return numberFormatter;
115
+ }
116
+ /** @private - Exported for tests */
117
+ function numberFormatSignDisplayPolyfill(numberFormat, signDisplay, num) {
118
+ if (signDisplay === "auto") return numberFormat.format(num);
119
+ else if (signDisplay === "never") return numberFormat.format(Math.abs(num));
120
+ else {
121
+ let needsPositiveSign = false;
122
+ if (signDisplay === "always") needsPositiveSign = num > 0 || Object.is(num, 0);
123
+ else if (signDisplay === "exceptZero") if (Object.is(num, -0) || Object.is(num, 0)) num = Math.abs(num);
124
+ else needsPositiveSign = num > 0;
125
+ if (needsPositiveSign) {
126
+ let negative = numberFormat.format(-num);
127
+ let noSign = numberFormat.format(num);
128
+ let minus = negative.replace(noSign, "").replace(/\u200e|\u061C/, "");
129
+ if ([...minus].length !== 1) console.warn("@react-aria/i18n polyfill for NumberFormat signDisplay: Unsupported case");
130
+ return negative.replace(noSign, "!!!").replace(minus, "+").replace("!!!", noSign);
131
+ } else return numberFormat.format(num);
132
+ }
133
+ }
134
+ //#endregion
135
+ export { NumberFormatter };
@@ -0,0 +1,30 @@
1
+ //#region src/number/NumberParser.d.ts
2
+ /**
3
+ * A NumberParser can be used to perform locale-aware parsing of numbers from Unicode strings,
4
+ * as well as validation of partial user input. It automatically detects the numbering system
5
+ * used in the input, and supports parsing decimals, percentages, currency values, and units
6
+ * according to the locale.
7
+ */
8
+ declare class NumberParser {
9
+ private locale;
10
+ private options;
11
+ constructor(locale: string, options?: Intl.NumberFormatOptions);
12
+ /**
13
+ * Parses the given string to a number. Returns NaN if a valid number could not be parsed.
14
+ */
15
+ parse(value: string): number;
16
+ /**
17
+ * Returns whether the given string could potentially be a valid number. This should be used to
18
+ * validate user input as the user types. If a `minValue` or `maxValue` is provided, the validity
19
+ * of the minus/plus sign characters can be checked.
20
+ */
21
+ isValidPartialNumber(value: string, minValue?: number, maxValue?: number): boolean;
22
+ /**
23
+ * Returns a numbering system for which the given string is valid in the current locale.
24
+ * If no numbering system could be detected, the default numbering system for the current
25
+ * locale is returned.
26
+ */
27
+ getNumberingSystem(value: string): string;
28
+ }
29
+ //#endregion
30
+ export { NumberParser };
@@ -0,0 +1,234 @@
1
+ import { NumberFormatter } from "./NumberFormatter.js";
2
+ //#region src/number/NumberParser.ts
3
+ const CURRENCY_SIGN_REGEX = /* @__PURE__ */ new RegExp("^.*\\(.*\\).*$");
4
+ const NUMBERING_SYSTEMS = [
5
+ "latn",
6
+ "arab",
7
+ "hanidec",
8
+ "deva",
9
+ "beng",
10
+ "fullwide"
11
+ ];
12
+ /**
13
+ * A NumberParser can be used to perform locale-aware parsing of numbers from Unicode strings,
14
+ * as well as validation of partial user input. It automatically detects the numbering system
15
+ * used in the input, and supports parsing decimals, percentages, currency values, and units
16
+ * according to the locale.
17
+ */
18
+ var NumberParser = class {
19
+ locale;
20
+ options;
21
+ constructor(locale, options = {}) {
22
+ this.locale = locale;
23
+ this.options = options;
24
+ }
25
+ /**
26
+ * Parses the given string to a number. Returns NaN if a valid number could not be parsed.
27
+ */
28
+ parse(value) {
29
+ return getNumberParserImpl(this.locale, this.options, value).parse(value);
30
+ }
31
+ /**
32
+ * Returns whether the given string could potentially be a valid number. This should be used to
33
+ * validate user input as the user types. If a `minValue` or `maxValue` is provided, the validity
34
+ * of the minus/plus sign characters can be checked.
35
+ */
36
+ isValidPartialNumber(value, minValue, maxValue) {
37
+ return getNumberParserImpl(this.locale, this.options, value).isValidPartialNumber(value, minValue, maxValue);
38
+ }
39
+ /**
40
+ * Returns a numbering system for which the given string is valid in the current locale.
41
+ * If no numbering system could be detected, the default numbering system for the current
42
+ * locale is returned.
43
+ */
44
+ getNumberingSystem(value) {
45
+ return getNumberParserImpl(this.locale, this.options, value).options.numberingSystem;
46
+ }
47
+ };
48
+ const numberParserCache = /* @__PURE__ */ new Map();
49
+ function getNumberParserImpl(locale, options, value) {
50
+ let defaultParser = getCachedNumberParser(locale, options);
51
+ if (!locale.includes("-nu-") && !defaultParser.isValidPartialNumber(value)) {
52
+ for (let numberingSystem of NUMBERING_SYSTEMS) if (numberingSystem !== defaultParser.options.numberingSystem) {
53
+ let parser = getCachedNumberParser(locale + (locale.includes("-u-") ? "-nu-" : "-u-nu-") + numberingSystem, options);
54
+ if (parser.isValidPartialNumber(value)) return parser;
55
+ }
56
+ }
57
+ return defaultParser;
58
+ }
59
+ function getCachedNumberParser(locale, options) {
60
+ let cacheKey = locale + (options ? Object.entries(options).sort((a, b) => a[0] < b[0] ? -1 : 1).join() : "");
61
+ let parser = numberParserCache.get(cacheKey);
62
+ if (!parser) {
63
+ parser = new NumberParserImpl(locale, options);
64
+ numberParserCache.set(cacheKey, parser);
65
+ }
66
+ return parser;
67
+ }
68
+ var NumberParserImpl = class {
69
+ formatter;
70
+ options;
71
+ symbols;
72
+ locale;
73
+ constructor(locale, options = {}) {
74
+ this.locale = locale;
75
+ if (options.roundingIncrement !== 1 && options.roundingIncrement != null) {
76
+ if (options.maximumFractionDigits == null && options.minimumFractionDigits == null) {
77
+ options.maximumFractionDigits = 0;
78
+ options.minimumFractionDigits = 0;
79
+ } else if (options.maximumFractionDigits == null) options.maximumFractionDigits = options.minimumFractionDigits;
80
+ else if (options.minimumFractionDigits == null) options.minimumFractionDigits = options.maximumFractionDigits;
81
+ }
82
+ this.formatter = new Intl.NumberFormat(locale, options);
83
+ this.options = this.formatter.resolvedOptions();
84
+ this.symbols = getSymbols(locale, this.formatter, this.options, options);
85
+ if (this.options.style === "percent" && ((this.options.minimumFractionDigits ?? 0) > 18 || (this.options.maximumFractionDigits ?? 0) > 18)) console.warn("NumberParser cannot handle percentages with greater than 18 decimal places, please reduce the number in your options.");
86
+ }
87
+ parse(value) {
88
+ let isGroupSymbolAllowed = this.formatter.resolvedOptions().useGrouping;
89
+ let fullySanitizedValue = this.sanitize(value);
90
+ if (!isGroupSymbolAllowed && this.symbols.group && fullySanitizedValue.includes(this.symbols.group)) return NaN;
91
+ else if (this.symbols.group) fullySanitizedValue = fullySanitizedValue.replaceAll(this.symbols.group, "");
92
+ if (this.symbols.decimal) fullySanitizedValue = fullySanitizedValue.replace(this.symbols.decimal, ".");
93
+ if (this.symbols.minusSign) fullySanitizedValue = fullySanitizedValue.replace(this.symbols.minusSign, "-");
94
+ fullySanitizedValue = fullySanitizedValue.replace(this.symbols.numeral, this.symbols.index);
95
+ if (this.options.style === "percent") {
96
+ let isNegative = fullySanitizedValue.indexOf("-");
97
+ fullySanitizedValue = fullySanitizedValue.replace("-", "");
98
+ fullySanitizedValue = fullySanitizedValue.replace("+", "");
99
+ let index = fullySanitizedValue.indexOf(".");
100
+ if (index === -1) index = fullySanitizedValue.length;
101
+ fullySanitizedValue = fullySanitizedValue.replace(".", "");
102
+ if (index - 2 === 0) fullySanitizedValue = `0.${fullySanitizedValue}`;
103
+ else if (index - 2 === -1) fullySanitizedValue = `0.0${fullySanitizedValue}`;
104
+ else if (index - 2 === -2) fullySanitizedValue = "0.00";
105
+ else fullySanitizedValue = `${fullySanitizedValue.slice(0, index - 2)}.${fullySanitizedValue.slice(index - 2)}`;
106
+ if (isNegative > -1) fullySanitizedValue = `-${fullySanitizedValue}`;
107
+ }
108
+ let newValue = fullySanitizedValue ? +fullySanitizedValue : NaN;
109
+ if (isNaN(newValue)) return NaN;
110
+ if (this.options.style === "percent") {
111
+ let options = {
112
+ ...this.options,
113
+ style: "decimal",
114
+ minimumFractionDigits: Math.min((this.options.minimumFractionDigits ?? 0) + 2, 20),
115
+ maximumFractionDigits: Math.min((this.options.maximumFractionDigits ?? 0) + 2, 20)
116
+ };
117
+ return new NumberParser(this.locale, options).parse(new NumberFormatter(this.locale, options).format(newValue));
118
+ }
119
+ if (this.options.currencySign === "accounting" && CURRENCY_SIGN_REGEX.test(value)) newValue = -1 * newValue;
120
+ return newValue;
121
+ }
122
+ sanitize(value) {
123
+ let isGroupSymbolAllowed = this.formatter.resolvedOptions().useGrouping;
124
+ if (this.symbols.noNumeralUnits.length > 0 && this.symbols.noNumeralUnits.find((obj) => obj.unit === value)) return this.symbols.noNumeralUnits.find((obj) => obj.unit === value).value.toString();
125
+ value = value.replace(this.symbols.literals, "");
126
+ if (this.symbols.minusSign) value = value.replace("-", this.symbols.minusSign);
127
+ if (this.options.numberingSystem === "arab") {
128
+ if (this.symbols.decimal) {
129
+ value = replaceAll(value, ",", this.symbols.decimal);
130
+ value = replaceAll(value, String.fromCharCode(1548), this.symbols.decimal);
131
+ }
132
+ if (this.symbols.group && isGroupSymbolAllowed) value = replaceAll(value, ".", this.symbols.group);
133
+ }
134
+ if (this.symbols.group === "’" && value.includes("'") && isGroupSymbolAllowed) value = replaceAll(value, "'", this.symbols.group);
135
+ if (this.symbols.group === "'" && value.includes("’") && isGroupSymbolAllowed) value = replaceAll(value, "’", this.symbols.group);
136
+ if (this.options.locale === "fr-FR" && this.symbols.group && isGroupSymbolAllowed) {
137
+ value = replaceAll(value, " ", this.symbols.group);
138
+ value = replaceAll(value, /\u00A0/g, this.symbols.group);
139
+ }
140
+ return value;
141
+ }
142
+ isValidPartialNumber(value, minValue = -Infinity, maxValue = Infinity) {
143
+ let isGroupSymbolAllowed = this.formatter.resolvedOptions().useGrouping;
144
+ value = this.sanitize(value);
145
+ if (this.symbols.minusSign && value.startsWith(this.symbols.minusSign) && minValue < 0) value = value.slice(this.symbols.minusSign.length);
146
+ else if (this.symbols.plusSign && value.startsWith(this.symbols.plusSign) && maxValue > 0) value = value.slice(this.symbols.plusSign.length);
147
+ if (this.symbols.decimal && value.indexOf(this.symbols.decimal) > -1 && this.options.maximumFractionDigits === 0) return false;
148
+ if (this.symbols.group && isGroupSymbolAllowed) value = replaceAll(value, this.symbols.group, "");
149
+ value = value.replace(this.symbols.numeral, "");
150
+ if (this.symbols.decimal) value = value.replace(this.symbols.decimal, "");
151
+ return value.length === 0;
152
+ }
153
+ };
154
+ const nonLiteralParts = new Set([
155
+ "decimal",
156
+ "fraction",
157
+ "integer",
158
+ "minusSign",
159
+ "plusSign",
160
+ "group"
161
+ ]);
162
+ const pluralNumbers = [
163
+ 0,
164
+ 4,
165
+ 2,
166
+ 1,
167
+ 11,
168
+ 20,
169
+ 3,
170
+ 7,
171
+ 100,
172
+ 21,
173
+ .1,
174
+ 1.1
175
+ ];
176
+ function getSymbols(locale, formatter, intlOptions, originalOptions) {
177
+ let symbolFormatter = new Intl.NumberFormat(locale, {
178
+ ...intlOptions,
179
+ minimumSignificantDigits: 1,
180
+ maximumSignificantDigits: 21,
181
+ roundingIncrement: 1,
182
+ roundingPriority: "auto",
183
+ roundingMode: "halfExpand",
184
+ useGrouping: true
185
+ });
186
+ let allParts = symbolFormatter.formatToParts(-10000.111);
187
+ let posAllParts = symbolFormatter.formatToParts(10000.111);
188
+ let pluralParts = pluralNumbers.map((n) => symbolFormatter.formatToParts(n));
189
+ let noNumeralUnits = pluralParts.map((p, i) => {
190
+ let unit = p.find((p) => p.type === "unit");
191
+ if (unit && !p.some((p) => p.type === "integer" || p.type === "fraction")) return {
192
+ unit: unit.value,
193
+ value: pluralNumbers[i]
194
+ };
195
+ return null;
196
+ }).filter((p) => !!p);
197
+ let minusSign = allParts.find((p) => p.type === "minusSign")?.value ?? "-";
198
+ let plusSign = posAllParts.find((p) => p.type === "plusSign")?.value;
199
+ if (!plusSign && (originalOptions?.signDisplay === "exceptZero" || originalOptions?.signDisplay === "always")) plusSign = "+";
200
+ let decimal = new Intl.NumberFormat(locale, {
201
+ ...intlOptions,
202
+ minimumFractionDigits: 2,
203
+ maximumFractionDigits: 2
204
+ }).formatToParts(.001).find((p) => p.type === "decimal")?.value;
205
+ let group = allParts.find((p) => p.type === "group")?.value;
206
+ let allPartsLiterals = allParts.filter((p) => !nonLiteralParts.has(p.type)).map((p) => escapeRegex(p.value));
207
+ let pluralPartsLiterals = pluralParts.flatMap((p) => p.filter((p) => !nonLiteralParts.has(p.type)).map((p) => escapeRegex(p.value)));
208
+ let sortedLiterals = [...new Set([...allPartsLiterals, ...pluralPartsLiterals])].sort((a, b) => b.length - a.length);
209
+ let literals = sortedLiterals.length === 0 ? /* @__PURE__ */ new RegExp("\\p{White_Space}|\\p{Cf}", "gu") : new RegExp(`${sortedLiterals.join("|")}|\\p{White_Space}|\\p{Cf}`, "gu");
210
+ let numerals = [...new Intl.NumberFormat(intlOptions.locale, { useGrouping: false }).format(9876543210)].reverse();
211
+ let indexes = new Map(numerals.map((d, i) => [d, i]));
212
+ let numeral = new RegExp(`[${numerals.join("")}]`, "g");
213
+ let index = (d) => String(indexes.get(d));
214
+ return {
215
+ minusSign,
216
+ plusSign,
217
+ decimal,
218
+ group,
219
+ literals,
220
+ numeral,
221
+ numerals,
222
+ index,
223
+ noNumeralUnits
224
+ };
225
+ }
226
+ function replaceAll(str, find, replace) {
227
+ if (str.replaceAll) return str.replaceAll(find, replace);
228
+ return str.split(find).join(replace);
229
+ }
230
+ function escapeRegex(string) {
231
+ return string.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
232
+ }
233
+ //#endregion
234
+ export { NumberParser };
@@ -0,0 +1,3 @@
1
+ import { NumberFormatOptions, NumberFormatter } from "./number/NumberFormatter.js";
2
+ import { NumberParser } from "./number/NumberParser.js";
3
+ export { type NumberFormatOptions, NumberFormatter, NumberParser };
package/dist/number.js ADDED
@@ -0,0 +1,3 @@
1
+ import { NumberFormatter } from "./number/NumberFormatter.js";
2
+ import { NumberParser } from "./number/NumberParser.js";
3
+ export { NumberFormatter, NumberParser };
@@ -0,0 +1,17 @@
1
+ import { DateFormatter } from "./date/DateFormatter.js";
2
+ import { useIsSSR } from "./ssr/use-is-ssr.js";
3
+ import { LocalizedStringDictionary, LocalizedStrings } from "./string/LocalizedStringDictionary.js";
4
+ import { LocalizedString, LocalizedStringFormatter, Variables } from "./string/LocalizedStringFormatter.js";
5
+ import { Locale, TextDirection, isRTL } from "./i18n/utils.js";
6
+ import { LocaleContext, LocaleContextValue, useLocaleContext } from "./i18n/context.js";
7
+ import { useCollator } from "./i18n/use-collator.js";
8
+ import { DateFormatterOptions, useDateFormatter } from "./i18n/use-date-formatter.js";
9
+ import { useDeepMemo } from "./i18n/use-deep-memo.js";
10
+ import { Filter, useFilter } from "./i18n/use-filter.js";
11
+ import { useListFormatter } from "./i18n/use-list-formatter.js";
12
+ import { useLocale } from "./i18n/use-locale.js";
13
+ import { useLocalizedStringDictionary, useLocalizedStringFormatter } from "./i18n/use-localized-string-formatter.js";
14
+ import { NumberFormatter } from "./number/NumberFormatter.js";
15
+ import { NumberParser } from "./number/NumberParser.js";
16
+ import { NumberFormatOptions, useNumberFormatter } from "./i18n/use-number-formatter.js";
17
+ export { DateFormatter, type DateFormatterOptions, type Filter, type Locale, LocaleContext, type LocaleContextValue, type LocalizedString, LocalizedStringDictionary, LocalizedStringFormatter, type LocalizedStrings, type NumberFormatOptions, NumberFormatter, NumberParser, type TextDirection, type Variables, isRTL, useCollator, useDateFormatter, useDeepMemo, useFilter, useIsSSR, useListFormatter, useLocale, useLocaleContext, useLocalizedStringDictionary, useLocalizedStringFormatter, useNumberFormatter };
package/dist/react.js ADDED
@@ -0,0 +1,17 @@
1
+ import { DateFormatter } from "./date/DateFormatter.js";
2
+ import { NumberFormatter } from "./number/NumberFormatter.js";
3
+ import { NumberParser } from "./number/NumberParser.js";
4
+ import { LocalizedStringDictionary } from "./string/LocalizedStringDictionary.js";
5
+ import { LocalizedStringFormatter } from "./string/LocalizedStringFormatter.js";
6
+ import { isRTL } from "./i18n/utils.js";
7
+ import { useIsSSR } from "./ssr/use-is-ssr.js";
8
+ import { LocaleContext, useLocaleContext } from "./i18n/context.js";
9
+ import { useLocale } from "./i18n/use-locale.js";
10
+ import { useCollator } from "./i18n/use-collator.js";
11
+ import { useDeepMemo } from "./i18n/use-deep-memo.js";
12
+ import { useDateFormatter } from "./i18n/use-date-formatter.js";
13
+ import { useFilter } from "./i18n/use-filter.js";
14
+ import { useListFormatter } from "./i18n/use-list-formatter.js";
15
+ import { useLocalizedStringDictionary, useLocalizedStringFormatter } from "./i18n/use-localized-string-formatter.js";
16
+ import { useNumberFormatter } from "./i18n/use-number-formatter.js";
17
+ export { DateFormatter, LocaleContext, LocalizedStringDictionary, LocalizedStringFormatter, NumberFormatter, NumberParser, isRTL, useCollator, useDateFormatter, useDeepMemo, useFilter, useIsSSR, useListFormatter, useLocale, useLocaleContext, useLocalizedStringDictionary, useLocalizedStringFormatter, useNumberFormatter };
@@ -0,0 +1,8 @@
1
+ //#region src/ssr/use-is-ssr.d.ts
2
+ /**
3
+ * Returns whether the component is server-rendering or hydrating, so browser-only rendering can be
4
+ * deferred until after hydration. Re-implemented from Adobe's react-aria (Apache-2.0; see NOTICE.txt).
5
+ */
6
+ declare function useIsSSR(): boolean;
7
+ //#endregion
8
+ export { useIsSSR };
@@ -0,0 +1,15 @@
1
+ "use client";
2
+ import { useSyncExternalStore } from "react";
3
+ //#region src/ssr/use-is-ssr.ts
4
+ const subscribe = () => () => {};
5
+ const getSnapshot = () => false;
6
+ const getServerSnapshot = () => true;
7
+ /**
8
+ * Returns whether the component is server-rendering or hydrating, so browser-only rendering can be
9
+ * deferred until after hydration. Re-implemented from Adobe's react-aria (Apache-2.0; see NOTICE.txt).
10
+ */
11
+ function useIsSSR() {
12
+ return useSyncExternalStore(subscribe, getSnapshot, getServerSnapshot);
13
+ }
14
+ //#endregion
15
+ export { useIsSSR };
@@ -0,0 +1,22 @@
1
+ import { LocalizedString } from "./LocalizedStringFormatter.js";
2
+
3
+ //#region src/string/LocalizedStringDictionary.d.ts
4
+ type LocalizedStrings<K extends string, T extends LocalizedString> = {
5
+ [lang: string]: Record<K, T>;
6
+ };
7
+ /**
8
+ * Stores a mapping of localized strings. Can be used to find the
9
+ * closest available string for a given locale.
10
+ */
11
+ declare class LocalizedStringDictionary<K extends string = string, T extends LocalizedString = string> {
12
+ private strings;
13
+ private defaultLocale;
14
+ constructor(messages: LocalizedStrings<K, T>, defaultLocale?: string);
15
+ /** Returns a localized string for the given key and locale. */
16
+ getStringForLocale(key: K, locale: string): T;
17
+ /** Returns all localized strings for the given locale. */
18
+ getStringsForLocale(locale: string): Record<K, T>;
19
+ static getGlobalDictionaryForPackage<K extends string = string, T extends LocalizedString = string>(packageName: string): LocalizedStringDictionary<K, T> | null;
20
+ }
21
+ //#endregion
22
+ export { LocalizedStringDictionary, LocalizedStrings };
@@ -0,0 +1,58 @@
1
+ //#region src/string/LocalizedStringDictionary.ts
2
+ const localeSymbol = Symbol.for("react-aria.i18n.locale");
3
+ const stringsSymbol = Symbol.for("react-aria.i18n.strings");
4
+ let cachedGlobalStrings = void 0;
5
+ /**
6
+ * Stores a mapping of localized strings. Can be used to find the
7
+ * closest available string for a given locale.
8
+ */
9
+ var LocalizedStringDictionary = class LocalizedStringDictionary {
10
+ strings;
11
+ defaultLocale;
12
+ constructor(messages, defaultLocale = "en-US") {
13
+ this.strings = Object.fromEntries(Object.entries(messages).filter(([, v]) => v));
14
+ this.defaultLocale = defaultLocale;
15
+ }
16
+ /** Returns a localized string for the given key and locale. */
17
+ getStringForLocale(key, locale) {
18
+ let string = this.getStringsForLocale(locale)[key];
19
+ if (!string) throw new Error(`Could not find intl message ${key} in ${locale} locale`);
20
+ return string;
21
+ }
22
+ /** Returns all localized strings for the given locale. */
23
+ getStringsForLocale(locale) {
24
+ let strings = this.strings[locale];
25
+ if (!strings) {
26
+ strings = getStringsForLocale(locale, this.strings, this.defaultLocale);
27
+ this.strings[locale] = strings;
28
+ }
29
+ return strings;
30
+ }
31
+ static getGlobalDictionaryForPackage(packageName) {
32
+ if (typeof window === "undefined") return null;
33
+ let globalWindow = window;
34
+ let locale = globalWindow[localeSymbol];
35
+ if (cachedGlobalStrings === void 0) {
36
+ let globalStrings = globalWindow[stringsSymbol];
37
+ if (!globalStrings) return null;
38
+ cachedGlobalStrings = {};
39
+ for (let pkg in globalStrings) cachedGlobalStrings[pkg] = new LocalizedStringDictionary({ [locale]: globalStrings[pkg] }, locale);
40
+ }
41
+ let dictionary = cachedGlobalStrings?.[packageName];
42
+ if (!dictionary) throw new Error(`Strings for package "${packageName}" were not included by LocalizedStringProvider. Please add it to the list passed to createLocalizedStringDictionary.`);
43
+ return dictionary;
44
+ }
45
+ };
46
+ function getStringsForLocale(locale, strings, defaultLocale = "en-US") {
47
+ if (strings[locale]) return strings[locale];
48
+ let language = getLanguage(locale);
49
+ if (strings[language]) return strings[language];
50
+ for (let key in strings) if (key.startsWith(language + "-")) return strings[key];
51
+ return strings[defaultLocale];
52
+ }
53
+ function getLanguage(locale) {
54
+ if (Intl.Locale) return new Intl.Locale(locale).language;
55
+ return locale.split("-")[0];
56
+ }
57
+ //#endregion
58
+ export { LocalizedStringDictionary };
@@ -0,0 +1,22 @@
1
+ import { LocalizedStringDictionary } from "./LocalizedStringDictionary.js";
2
+
3
+ //#region src/string/LocalizedStringFormatter.d.ts
4
+ type Variables = Record<string, string | number | boolean> | undefined;
5
+ type LocalizedString = string | ((args: Variables, formatter?: LocalizedStringFormatter<any, any>) => string);
6
+ type InternalString = string | (() => string);
7
+ /**
8
+ * Formats localized strings from a LocalizedStringDictionary. Supports interpolating variables,
9
+ * selecting the correct pluralization, and formatting numbers for the locale.
10
+ */
11
+ declare class LocalizedStringFormatter<K extends string = string, T extends LocalizedString = string> {
12
+ private locale;
13
+ private strings;
14
+ constructor(locale: string, strings: LocalizedStringDictionary<K, T>);
15
+ /** Formats a localized string for the given key with the provided variables. */
16
+ format(key: K, variables?: Variables): string;
17
+ protected plural(count: number, options: Record<string, InternalString>, type?: Intl.PluralRuleType): string;
18
+ protected number(value: number): string;
19
+ protected select(options: Record<string, InternalString>, value: string): string;
20
+ }
21
+ //#endregion
22
+ export { LocalizedString, LocalizedStringFormatter, Variables };
@@ -0,0 +1,46 @@
1
+ //#region src/string/LocalizedStringFormatter.ts
2
+ const pluralRulesCache = /* @__PURE__ */ new Map();
3
+ const numberFormatCache = /* @__PURE__ */ new Map();
4
+ /**
5
+ * Formats localized strings from a LocalizedStringDictionary. Supports interpolating variables,
6
+ * selecting the correct pluralization, and formatting numbers for the locale.
7
+ */
8
+ var LocalizedStringFormatter = class {
9
+ locale;
10
+ strings;
11
+ constructor(locale, strings) {
12
+ this.locale = locale;
13
+ this.strings = strings;
14
+ }
15
+ /** Formats a localized string for the given key with the provided variables. */
16
+ format(key, variables) {
17
+ let message = this.strings.getStringForLocale(key, this.locale);
18
+ return typeof message === "function" ? message(variables, this) : message;
19
+ }
20
+ plural(count, options, type = "cardinal") {
21
+ let opt = options["=" + count];
22
+ if (opt) return typeof opt === "function" ? opt() : opt;
23
+ let key = this.locale + ":" + type;
24
+ let pluralRules = pluralRulesCache.get(key);
25
+ if (!pluralRules) {
26
+ pluralRules = new Intl.PluralRules(this.locale, { type });
27
+ pluralRulesCache.set(key, pluralRules);
28
+ }
29
+ opt = options[pluralRules.select(count)] || options.other;
30
+ return typeof opt === "function" ? opt() : opt;
31
+ }
32
+ number(value) {
33
+ let numberFormat = numberFormatCache.get(this.locale);
34
+ if (!numberFormat) {
35
+ numberFormat = new Intl.NumberFormat(this.locale);
36
+ numberFormatCache.set(this.locale, numberFormat);
37
+ }
38
+ return numberFormat.format(value);
39
+ }
40
+ select(options, value) {
41
+ let opt = options[value] || options.other;
42
+ return typeof opt === "function" ? opt() : opt;
43
+ }
44
+ };
45
+ //#endregion
46
+ export { LocalizedStringFormatter };
@@ -0,0 +1,3 @@
1
+ import { LocalizedStringDictionary, LocalizedStrings } from "./string/LocalizedStringDictionary.js";
2
+ import { LocalizedString, LocalizedStringFormatter, Variables } from "./string/LocalizedStringFormatter.js";
3
+ export { type LocalizedString, LocalizedStringDictionary, LocalizedStringFormatter, type LocalizedStrings, type Variables };