@reykjavik/webtools 0.1.14 → 0.1.16

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/CHANGELOG.md CHANGED
@@ -4,18 +4,29 @@
4
4
 
5
5
  - ... <!-- Add new lines here. -->
6
6
 
7
- ## 0.1.14
7
+ ## 0.1.16
8
8
 
9
- _2024-03-06_
9
+ _2024-03-09_
10
+
11
+ - `@reykjavik/webtools/fixIcelandicLocale`:
12
+ - fix: Add missing `DateTimeFormat.format*ToParts` methods, fix bugs
13
+ - refctor: Reduce code-size and simplify logic by dog-fooding `*ToParts`
14
+ methods internally
15
+
16
+ ## 0.1.15
17
+
18
+ _2024-03-08_
10
19
 
11
- - fix: Remove unneccessary `@__PURE__` pragma
12
- - fix: List `fixIcelandicLocale.js` in `pkg.sideEffects`
20
+ - `@reykjavik/webtools/fixIcelandicLocale`:
21
+ - feat: Patch `Intl.Collator`
22
+ - feat: Patch `Intl.NumberFormat` and `Number.prototype.toLocaleString`
23
+ - feat: Patch `Intl.DateTimeFormat` and `Date.prototype.toLocaleDateString`
13
24
 
14
- ## 0.1.13
25
+ ## 0.1.13 – 0.1.14
15
26
 
16
27
  _2024-03-06_
17
28
 
18
- - feat: Add `@reykjavik/webtools/fixIcelandicLocale` polyfill module
29
+ - feat: Add `@reykjavik/webtools/fixIcelandicLocale` Chrome paching module
19
30
 
20
31
  ## 0.1.12
21
32
 
@@ -35,7 +46,7 @@ _2024-02-14_
35
46
 
36
47
  _2024-02-14_
37
48
 
38
- - `@reykjavik/webtools/http`
49
+ - `@reykjavik/webtools/http`:
39
50
  - feat: Add the rest of the more obscure HTTP status constants (WebDAV,
40
51
  etc.)
41
52
  - fix: Add `429`, `432`, `451` to `HTTP_CLIENT_ERROR_ALL` type
@@ -44,7 +55,7 @@ _2024-02-14_
44
55
 
45
56
  _2023-04-27_
46
57
 
47
- - `@reykjavik/webtools/http`
58
+ - `@reykjavik/webtools/http`:
48
59
  - feat: Add `toSec` TTL helper
49
60
 
50
61
  ## 0.1.7 – 0.1.8
package/README.md CHANGED
@@ -32,6 +32,7 @@ bun add @reykjavik/webtools
32
32
  - [`vanillaClassNested`](#vanillaclassnested)
33
33
  - [`vanillaNest`](#vanillanest)
34
34
  - [`@reykjavik/webtools/fixIcelandicLocale`](#reykjavikwebtoolsfixicelandiclocale)
35
+ - [Limitations](#limitations)
35
36
  - [Framework Specific Tools](#framework-specific-tools)
36
37
  - [Next.js Tools](#nextjs-tools)
37
38
  - [Contributing](#contributing)
@@ -427,21 +428,58 @@ vanillaGlobal(`
427
428
 
428
429
  ## `@reykjavik/webtools/fixIcelandicLocale`
429
430
 
430
- Polyfill for `String.prototype.localeCompare` to provide support for the
431
- `'is'` locale in browsers that don't support it (\*cough* Chrome \*cough*).
431
+ As of early 2004, Google Chrome still does not support the Icelandic locale
432
+ `is`/`is-IS` in any way. Meanwhile other browsers have supported it for over a
433
+ decade.
432
434
 
433
- At the top of your app's entry point, "side-effect import" this module to
434
- apply the polyfill:
435
+ This module does attempts to patches the following methods/classes by
436
+ substituting the `is` locale with `da` (Danish) and apply a few post-hoc fixes
437
+ to their return values.
438
+
439
+ - `Intl.Collator` and `String.prototype.localeCompare`
440
+ - `Intl.NumberFormat` and `Number.prototype.toLocaleString`
441
+ - `Intl.DateTimeFormat` and `Date.prototype.toLocaleDateString`
442
+
443
+ This provides usable (but not perfect) results, with some caveats listed
444
+ below.
445
+
446
+ To apply these patches, simply "side-effect import" this module at the top of
447
+ your app's entry point:
435
448
 
436
449
  ```ts
437
450
  import '@reykjavik/webtools/fixIcelandicLocale';
438
451
 
439
- // Then continue with your day
440
- // and use `localeCompare` as you normally would...
452
+ // Then continue with your day and use `localeCompare` and Intl.Collator,
453
+ // as you normally would. (See "limitations" below.)
441
454
  ```
442
455
 
443
- **NOTE** The polyfill is only applied in engines that fail a simple feature
444
- test.
456
+ (**NOTE** The patch is only applied in engines that fail a simple feature
457
+ detection test.)
458
+
459
+ ### Limitations
460
+
461
+ **`Intl.Collator` and `localeCompare`:**
462
+
463
+ - When the `sensitivty` option is set to `"base"` or `"accent"`, it will
464
+ incorrectly treat `ð` and `d` as the same letter, and the acute-accented
465
+ characters `á`, `é`, `í`, `ó`, `ú` and `ý` get lumped in with their
466
+ non-accented counterparts.
467
+
468
+ **`Intl.NumberFormat` and `toLocaleString`:**
469
+
470
+ - The `style: "unit"` option is not supported and prints units in Danish. (Soo
471
+ many units and unit-variants…)
472
+ - The `currencyDisplay: "name"` option is not supported and prints the
473
+ currency's full name in Danish.
474
+
475
+ **`Intl.DateTimeFormat` and `toLocaleDateString`:**
476
+
477
+ - The `month: 'narrow'` and `weekday: 'narrow'` options are not supported, and
478
+ print the corresponding Danish initials.
479
+ - For `timeZoneName` the values `"long"`, `"shortGeneric"` and `"longGeneric"`
480
+ will appear in Danish.
481
+ - The `timeStyle: 'full'` option prints the timezone names in Danish
482
+ - The `dayPeriod` option is not supported and prints the day-period in Danish.
445
483
 
446
484
  ---
447
485
 
@@ -1,39 +1,14 @@
1
+ import { _PatchedCollator, _PatchedDateTimeFormat, _patchedLocaleCompare, _PatchedNumberFormat, _patchedToLocaleDateString, _patchedToLocaleString, } from './fixIcelandicLocale.privates.js';
1
2
  /*
2
- TODO: Also attempt to patch these Classes:
3
-
4
- - `Intl.Collator`
5
- Same kind of magic as `String.prototype.localeCompare`
6
-
7
- - `Intl.NumberFormat`
8
- Only support default plain-number and percent formatting,
9
- not currency or unit — UNLESS we do some string-replacement hackery,
10
- and then we may need to handle the possibility of locales arrays
11
- already containing Danish ahead of "is".
12
-
13
- - `Intl.DateTimeFormat`
14
- Possible with some mad-scientist string-replacement hackery and
15
- the `locales` array detection magic, as mentioned above.
16
-
17
- Mantra: Partial Icelandic suppoort is better than no Icelandic support.
3
+ Mantra: Partial Icelandic suppoort is better than none. Partial Icelandic
4
+ suppoort is better than none. Partial Icelandic suppoort is better than
5
+ none. Partial Icelandic suppoort is better than none. Partial Icelandic...
18
6
  */
19
- const locAliases = {
20
- // Danish is good enough substitution for Icelandic
21
- is: 'da',
22
- 'is-is': 'da',
23
- };
24
- const mapLocales = (locales) => {
25
- locales = typeof locales === 'string' ? [locales] : locales || [];
26
- return locales.map((loc) => locAliases[loc.toLowerCase()] || loc);
27
- };
28
- // ---------------------------------------------------------------------------
29
- if ('ö'.localeCompare('p', 'is') < 0) {
30
- const _localeCompare = String.prototype.localeCompare;
31
- /**
32
- * Polyfill for String.prototype.localeCompare for the 'is' locale
33
- * in browsers that don't support it (\*cough* Chrome \*cough*).
34
- */
35
- String.prototype.localeCompare = function (that, locales, options) {
36
- return _localeCompare.call(this, that, mapLocales(locales), options);
37
- };
7
+ if (Intl.Collator.supportedLocalesOf(['is']).length < 1) {
8
+ Intl.Collator = _PatchedCollator;
9
+ String.prototype.localeCompare = _patchedLocaleCompare;
10
+ Intl.NumberFormat = _PatchedNumberFormat;
11
+ Number.prototype.toLocaleString = _patchedToLocaleString;
12
+ Intl.DateTimeFormat = _PatchedDateTimeFormat;
13
+ Date.prototype.toLocaleDateString = _patchedToLocaleDateString;
38
14
  }
39
- export {};
@@ -0,0 +1,45 @@
1
+ export declare const _PatchedCollator: {
2
+ (locales?: string | string[] | undefined, options?: Intl.CollatorOptions | undefined): Intl.Collator;
3
+ new (locales?: string | string[] | undefined, options?: Intl.CollatorOptions | undefined): Intl.Collator;
4
+ supportedLocalesOf(locales: string | string[], options?: Intl.CollatorOptions | undefined): string[];
5
+ } & {
6
+ $original: typeof Intl.Collator;
7
+ };
8
+ export declare const _patchedLocaleCompare: {
9
+ (this: string, that: string, locales?: string | Array<string>, options?: Intl.CollatorOptions): number;
10
+ $original: {
11
+ (that: string): number;
12
+ (that: string, locales?: string | string[] | undefined, options?: Intl.CollatorOptions | undefined): number;
13
+ };
14
+ };
15
+ export declare const _PatchedNumberFormat: {
16
+ (locales?: string | string[] | undefined, options?: Intl.NumberFormatOptions | undefined): Intl.NumberFormat;
17
+ new (locales?: string | string[] | undefined, options?: Intl.NumberFormatOptions | undefined): Intl.NumberFormat;
18
+ supportedLocalesOf(locales: string | string[], options?: Intl.NumberFormatOptions | undefined): string[];
19
+ readonly prototype: Intl.NumberFormat;
20
+ } & {
21
+ $original: typeof Intl.NumberFormat;
22
+ };
23
+ export declare const _patchedToLocaleString: {
24
+ (this: number, locales?: string | Array<string>, options?: Intl.NumberFormatOptions): string;
25
+ $original: {
26
+ (locales?: string | string[] | undefined, options?: Intl.NumberFormatOptions | undefined): string;
27
+ (locales?: Intl.LocalesArgument, options?: Intl.NumberFormatOptions | undefined): string;
28
+ };
29
+ };
30
+ export declare const _PatchedDateTimeFormat: {
31
+ (locales?: string | string[] | undefined, options?: Intl.DateTimeFormatOptions | undefined): Intl.DateTimeFormat;
32
+ new (locales?: string | string[] | undefined, options?: Intl.DateTimeFormatOptions | undefined): Intl.DateTimeFormat;
33
+ supportedLocalesOf(locales: string | string[], options?: Intl.DateTimeFormatOptions | undefined): string[];
34
+ readonly prototype: Intl.DateTimeFormat;
35
+ } & {
36
+ $original: typeof Intl.DateTimeFormat;
37
+ };
38
+ export declare const _patchedToLocaleDateString: {
39
+ (this: Date, locales?: string | Array<string>, options?: Intl.DateTimeFormatOptions): string;
40
+ $original: {
41
+ (): string;
42
+ (locales?: string | string[] | undefined, options?: Intl.DateTimeFormatOptions | undefined): string;
43
+ (locales?: Intl.LocalesArgument, options?: Intl.DateTimeFormatOptions | undefined): string;
44
+ };
45
+ };
@@ -0,0 +1,226 @@
1
+ const _Collator = Intl.Collator;
2
+ const _NumberFormat = Intl.NumberFormat;
3
+ const _DateTimeFormat = Intl.DateTimeFormat;
4
+ const mapLocales = (locales) => {
5
+ locales = typeof locales === 'string' ? [locales] : locales || [];
6
+ for (let i = 0, loc; (loc = locales[i]); i++) {
7
+ const isIsLocale = /^isl?(?:-|$)/i.test(loc);
8
+ if (isIsLocale) {
9
+ // Danish feels like a "good enough" substitution for Icelandic.
10
+ // For alphabetization, it seems to just the internal order of `Ø` and `Ö`
11
+ // that's different, and when the `sensitivity` option is set to "base"
12
+ // or "accent" then `ð` is consiered a variant of `d` and the letters
13
+ // á, é, í, ó, ú, and ý are not treated as separate letters but simply
14
+ // variants of a, e, i, o, u, and y.
15
+ return ['da'];
16
+ }
17
+ if (_Collator.supportedLocalesOf(loc).length) {
18
+ return; // no mapping needed. YOLO!
19
+ }
20
+ }
21
+ };
22
+ const combineParts = (parts) => parts.map(({ value }) => value).join('');
23
+ // ===========================================================================
24
+ // Collator
25
+ // ===========================================================================
26
+ const PatchedCollator = function Collator(locales, options) {
27
+ locales = mapLocales(locales) || locales;
28
+ const instance = new _Collator(locales, options);
29
+ Object.setPrototypeOf(instance, PatchedCollator.prototype);
30
+ return instance;
31
+ };
32
+ PatchedCollator.prototype = Object.create(_Collator.prototype);
33
+ PatchedCollator.prototype.constructor = PatchedCollator;
34
+ // Static methods (not patched since "is" is not ACTUALLY supported.)
35
+ PatchedCollator.supportedLocalesOf = _Collator.supportedLocalesOf;
36
+ PatchedCollator.$original = _Collator;
37
+ export const _PatchedCollator = PatchedCollator;
38
+ // ---------------------------------------------------------------------------
39
+ const _localeCompare = String.prototype.localeCompare;
40
+ export const _patchedLocaleCompare = function localeCompare(that, locales, options) {
41
+ return _PatchedCollator(locales, options).compare(this, that);
42
+ };
43
+ _patchedLocaleCompare.$original = _localeCompare;
44
+ // ===========================================================================
45
+ // NumberFormat
46
+ // ===========================================================================
47
+ const reformatNumberParts = function (parts) {
48
+ if (!this.mapped) {
49
+ return parts;
50
+ }
51
+ const options = this.super.resolvedOptions();
52
+ if (options.style === 'currency' && options.currencyDisplay === 'symbol') {
53
+ const currency = options.currency;
54
+ if (currency === 'DKK' || currency === 'ISK') {
55
+ parts.forEach((part) => {
56
+ if (part.type === 'currency') {
57
+ part.value = currency === 'DKK' ? 'DKK' : 'kr.';
58
+ }
59
+ });
60
+ }
61
+ }
62
+ return parts;
63
+ };
64
+ const PatchedNumberFormat = function NumberFormat(locales, options) {
65
+ if (!(this instanceof PatchedNumberFormat)) {
66
+ // @ts-expect-error (YOLO! Can't be arsed)
67
+ return new PatchedNumberFormat(locales, options);
68
+ }
69
+ const mappedLocales = mapLocales(locales);
70
+ this.super = _NumberFormat(mappedLocales || locales, options);
71
+ this.mapped = !!mappedLocales;
72
+ };
73
+ // This is all very hacky, but extending the class *AND* preseving the
74
+ // ability to instantiate without `new` is a bit of a pain.
75
+ // Eagerly interested in finding a better way to do this.
76
+ const numberFormatProto = {
77
+ constructor: PatchedNumberFormat,
78
+ format(value) {
79
+ return combineParts(this.formatToParts(value));
80
+ },
81
+ formatRange(value1, value2) {
82
+ return combineParts(this.formatRangeToParts(value1, value2));
83
+ },
84
+ formatToParts(value) {
85
+ return reformatNumberParts.call(this, this.super.formatToParts(value));
86
+ },
87
+ formatRangeToParts(value1, value2) {
88
+ return reformatNumberParts.call(this, this.super.formatRangeToParts(value1, value2));
89
+ },
90
+ resolvedOptions() {
91
+ return this.super.resolvedOptions();
92
+ },
93
+ };
94
+ PatchedNumberFormat.prototype = numberFormatProto;
95
+ // Static methods (not patched since "is" is not ACTUALLY supported.)
96
+ PatchedNumberFormat.supportedLocalesOf = _NumberFormat.supportedLocalesOf;
97
+ PatchedNumberFormat.$original = _NumberFormat;
98
+ export const _PatchedNumberFormat = PatchedNumberFormat;
99
+ // ---------------------------------------------------------------------------
100
+ const _toLocaleString = Number.prototype.toLocaleString;
101
+ export const _patchedToLocaleString = function toLocaleString(locales, options) {
102
+ return _PatchedNumberFormat(locales, options).format(this);
103
+ };
104
+ _patchedToLocaleString.$original = _toLocaleString;
105
+ // ===========================================================================
106
+ // DateTimeFormat
107
+ // ===========================================================================
108
+ const months = {
109
+ jan: 'janúar',
110
+ feb: 'febrúar',
111
+ mar: 'mars',
112
+ apr: 'apríl',
113
+ maj: 'maí',
114
+ jun: 'júní',
115
+ jul: 'júlí',
116
+ aug: 'ágúst',
117
+ // sep: 'september', // is the same
118
+ okt: 'október',
119
+ nov: 'nóvember',
120
+ dec: 'desember',
121
+ };
122
+ const weekdays = {
123
+ man: 'mánudagur',
124
+ tir: 'þriðjudagur',
125
+ ons: 'miðvikudagur',
126
+ tor: 'fimmtudagur',
127
+ fre: 'föstudagur',
128
+ lør: 'laugardagur',
129
+ søn: 'sunnudagur',
130
+ };
131
+ const partMappers = {
132
+ month: (value) => {
133
+ const islMonth = months[value.slice(0, 3)];
134
+ if (islMonth) {
135
+ return value.endsWith('.') ? `${islMonth.slice(0, 3)}.` : islMonth;
136
+ }
137
+ },
138
+ weekday: (value) => {
139
+ const isl = weekdays[value.slice(0, 3)];
140
+ if (isl) {
141
+ return value.endsWith('.') ? `${isl.slice(0, 3)}.` : isl;
142
+ }
143
+ },
144
+ era: (value) => {
145
+ if (!value.endsWith('.')) {
146
+ return value.length === 3
147
+ ? `${value[0]}.k.`
148
+ : value[0] === 'f'
149
+ ? 'fyrir Krist'
150
+ : 'eftir Krist';
151
+ }
152
+ },
153
+ dayPeriod: (value) => {
154
+ return { AM: 'f.h.', PM: 'e.h.' }[value] || value;
155
+ },
156
+ literal: (value, lastType) => {
157
+ if (value === ' den ') {
158
+ return 'inn ';
159
+ }
160
+ else if (value === '.' && (lastType === 'hour' || lastType === 'minute')) {
161
+ return ':';
162
+ }
163
+ },
164
+ };
165
+ const reformatDateTimeParts = function (parts) {
166
+ if (!this.mapped) {
167
+ return parts;
168
+ }
169
+ parts.forEach((part, idx) => {
170
+ var _a;
171
+ const mapper = partMappers[part.type];
172
+ const newValue = mapper && mapper(part.value, (_a = parts[idx - 1]) === null || _a === void 0 ? void 0 : _a.type);
173
+ if (newValue != null) {
174
+ part.value = newValue;
175
+ }
176
+ });
177
+ return parts;
178
+ };
179
+ const PatchedDateTimeFormat = function DateTimeFormat(locales, options) {
180
+ if (!(this instanceof PatchedDateTimeFormat)) {
181
+ // @ts-expect-error (YOLO! Can't be arsed)
182
+ return new PatchedDateTimeFormat(locales, options);
183
+ }
184
+ const mappedLocales = mapLocales(locales);
185
+ if (options === null || options === void 0 ? void 0 : options.hour12) {
186
+ options = {
187
+ ...options,
188
+ hour12: undefined,
189
+ hourCycle: 'h11',
190
+ };
191
+ }
192
+ this.super = _DateTimeFormat(mappedLocales || locales, options);
193
+ this.mapped = !!mappedLocales;
194
+ };
195
+ // This is all very hacky, but extending the class *AND* preseving the
196
+ // ability to instantiate without `new` is a bit of a pain.
197
+ // Eagerly interested in finding a better way to do this.
198
+ const dateTimeFormatProto = {
199
+ constructor: PatchedDateTimeFormat,
200
+ format(value) {
201
+ return combineParts(this.formatToParts(value));
202
+ },
203
+ formatRange(value1, value2) {
204
+ return combineParts(this.formatRangeToParts(value1, value2));
205
+ },
206
+ formatToParts(value) {
207
+ return reformatDateTimeParts.call(this, this.super.formatToParts(value));
208
+ },
209
+ formatRangeToParts(value1, value2) {
210
+ return reformatDateTimeParts.call(this, this.super.formatRangeToParts(value1, value2));
211
+ },
212
+ resolvedOptions() {
213
+ return this.super.resolvedOptions();
214
+ },
215
+ };
216
+ PatchedDateTimeFormat.prototype = dateTimeFormatProto;
217
+ // Static methods (not patched since "is" is not ACTUALLY supported.)
218
+ PatchedDateTimeFormat.supportedLocalesOf = _DateTimeFormat.supportedLocalesOf;
219
+ PatchedDateTimeFormat.$original = _DateTimeFormat;
220
+ export const _PatchedDateTimeFormat = PatchedDateTimeFormat;
221
+ // ---------------------------------------------------------------------------
222
+ const _toLocaleDateString = Date.prototype.toLocaleDateString;
223
+ export const _patchedToLocaleDateString = function toLocaleDateString(locales, options) {
224
+ return _PatchedDateTimeFormat(locales, options).format(this);
225
+ };
226
+ _patchedToLocaleDateString.$original = _toLocaleDateString;
@@ -1,2 +1 @@
1
- declare const locAliases: Record<string, string>;
2
- declare const mapLocales: (locales: string | Array<string> | undefined) => string[];
1
+ export {};
@@ -1,39 +1,16 @@
1
1
  "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ const fixIcelandicLocale_privates_js_1 = require("./fixIcelandicLocale.privates.js");
2
4
  /*
3
- TODO: Also attempt to patch these Classes:
4
-
5
- - `Intl.Collator`
6
- Same kind of magic as `String.prototype.localeCompare`
7
-
8
- - `Intl.NumberFormat`
9
- Only support default plain-number and percent formatting,
10
- not currency or unit — UNLESS we do some string-replacement hackery,
11
- and then we may need to handle the possibility of locales arrays
12
- already containing Danish ahead of "is".
13
-
14
- - `Intl.DateTimeFormat`
15
- Possible with some mad-scientist string-replacement hackery and
16
- the `locales` array detection magic, as mentioned above.
17
-
18
- Mantra: Partial Icelandic suppoort is better than no Icelandic support.
5
+ Mantra: Partial Icelandic suppoort is better than none. Partial Icelandic
6
+ suppoort is better than none. Partial Icelandic suppoort is better than
7
+ none. Partial Icelandic suppoort is better than none. Partial Icelandic...
19
8
  */
20
- const locAliases = {
21
- // Danish is good enough substitution for Icelandic
22
- is: 'da',
23
- 'is-is': 'da',
24
- };
25
- const mapLocales = (locales) => {
26
- locales = typeof locales === 'string' ? [locales] : locales || [];
27
- return locales.map((loc) => locAliases[loc.toLowerCase()] || loc);
28
- };
29
- // ---------------------------------------------------------------------------
30
- if ('ö'.localeCompare('p', 'is') < 0) {
31
- const _localeCompare = String.prototype.localeCompare;
32
- /**
33
- * Polyfill for String.prototype.localeCompare for the 'is' locale
34
- * in browsers that don't support it (\*cough* Chrome \*cough*).
35
- */
36
- String.prototype.localeCompare = function (that, locales, options) {
37
- return _localeCompare.call(this, that, mapLocales(locales), options);
38
- };
9
+ if (Intl.Collator.supportedLocalesOf(['is']).length < 1) {
10
+ Intl.Collator = fixIcelandicLocale_privates_js_1._PatchedCollator;
11
+ String.prototype.localeCompare = fixIcelandicLocale_privates_js_1._patchedLocaleCompare;
12
+ Intl.NumberFormat = fixIcelandicLocale_privates_js_1._PatchedNumberFormat;
13
+ Number.prototype.toLocaleString = fixIcelandicLocale_privates_js_1._patchedToLocaleString;
14
+ Intl.DateTimeFormat = fixIcelandicLocale_privates_js_1._PatchedDateTimeFormat;
15
+ Date.prototype.toLocaleDateString = fixIcelandicLocale_privates_js_1._patchedToLocaleDateString;
39
16
  }
@@ -0,0 +1,45 @@
1
+ export declare const _PatchedCollator: {
2
+ (locales?: string | string[] | undefined, options?: Intl.CollatorOptions | undefined): Intl.Collator;
3
+ new (locales?: string | string[] | undefined, options?: Intl.CollatorOptions | undefined): Intl.Collator;
4
+ supportedLocalesOf(locales: string | string[], options?: Intl.CollatorOptions | undefined): string[];
5
+ } & {
6
+ $original: typeof Intl.Collator;
7
+ };
8
+ export declare const _patchedLocaleCompare: {
9
+ (this: string, that: string, locales?: string | Array<string>, options?: Intl.CollatorOptions): number;
10
+ $original: {
11
+ (that: string): number;
12
+ (that: string, locales?: string | string[] | undefined, options?: Intl.CollatorOptions | undefined): number;
13
+ };
14
+ };
15
+ export declare const _PatchedNumberFormat: {
16
+ (locales?: string | string[] | undefined, options?: Intl.NumberFormatOptions | undefined): Intl.NumberFormat;
17
+ new (locales?: string | string[] | undefined, options?: Intl.NumberFormatOptions | undefined): Intl.NumberFormat;
18
+ supportedLocalesOf(locales: string | string[], options?: Intl.NumberFormatOptions | undefined): string[];
19
+ readonly prototype: Intl.NumberFormat;
20
+ } & {
21
+ $original: typeof Intl.NumberFormat;
22
+ };
23
+ export declare const _patchedToLocaleString: {
24
+ (this: number, locales?: string | Array<string>, options?: Intl.NumberFormatOptions): string;
25
+ $original: {
26
+ (locales?: string | string[] | undefined, options?: Intl.NumberFormatOptions | undefined): string;
27
+ (locales?: Intl.LocalesArgument, options?: Intl.NumberFormatOptions | undefined): string;
28
+ };
29
+ };
30
+ export declare const _PatchedDateTimeFormat: {
31
+ (locales?: string | string[] | undefined, options?: Intl.DateTimeFormatOptions | undefined): Intl.DateTimeFormat;
32
+ new (locales?: string | string[] | undefined, options?: Intl.DateTimeFormatOptions | undefined): Intl.DateTimeFormat;
33
+ supportedLocalesOf(locales: string | string[], options?: Intl.DateTimeFormatOptions | undefined): string[];
34
+ readonly prototype: Intl.DateTimeFormat;
35
+ } & {
36
+ $original: typeof Intl.DateTimeFormat;
37
+ };
38
+ export declare const _patchedToLocaleDateString: {
39
+ (this: Date, locales?: string | Array<string>, options?: Intl.DateTimeFormatOptions): string;
40
+ $original: {
41
+ (): string;
42
+ (locales?: string | string[] | undefined, options?: Intl.DateTimeFormatOptions | undefined): string;
43
+ (locales?: Intl.LocalesArgument, options?: Intl.DateTimeFormatOptions | undefined): string;
44
+ };
45
+ };
@@ -0,0 +1,232 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports._patchedToLocaleDateString = exports._PatchedDateTimeFormat = exports._patchedToLocaleString = exports._PatchedNumberFormat = exports._patchedLocaleCompare = exports._PatchedCollator = void 0;
4
+ const _Collator = Intl.Collator;
5
+ const _NumberFormat = Intl.NumberFormat;
6
+ const _DateTimeFormat = Intl.DateTimeFormat;
7
+ const mapLocales = (locales) => {
8
+ locales = typeof locales === 'string' ? [locales] : locales || [];
9
+ for (let i = 0, loc; (loc = locales[i]); i++) {
10
+ const isIsLocale = /^isl?(?:-|$)/i.test(loc);
11
+ if (isIsLocale) {
12
+ // Danish feels like a "good enough" substitution for Icelandic.
13
+ // For alphabetization, it seems to just the internal order of `Ø` and `Ö`
14
+ // that's different, and when the `sensitivity` option is set to "base"
15
+ // or "accent" then `ð` is consiered a variant of `d` and the letters
16
+ // á, é, í, ó, ú, and ý are not treated as separate letters but simply
17
+ // variants of a, e, i, o, u, and y.
18
+ return ['da'];
19
+ }
20
+ if (_Collator.supportedLocalesOf(loc).length) {
21
+ return; // no mapping needed. YOLO!
22
+ }
23
+ }
24
+ };
25
+ const combineParts = (parts) => parts.map(({ value }) => value).join('');
26
+ // ===========================================================================
27
+ // Collator
28
+ // ===========================================================================
29
+ const PatchedCollator = function Collator(locales, options) {
30
+ locales = mapLocales(locales) || locales;
31
+ const instance = new _Collator(locales, options);
32
+ Object.setPrototypeOf(instance, PatchedCollator.prototype);
33
+ return instance;
34
+ };
35
+ PatchedCollator.prototype = Object.create(_Collator.prototype);
36
+ PatchedCollator.prototype.constructor = PatchedCollator;
37
+ // Static methods (not patched since "is" is not ACTUALLY supported.)
38
+ PatchedCollator.supportedLocalesOf = _Collator.supportedLocalesOf;
39
+ PatchedCollator.$original = _Collator;
40
+ exports._PatchedCollator = PatchedCollator;
41
+ // ---------------------------------------------------------------------------
42
+ const _localeCompare = String.prototype.localeCompare;
43
+ const _patchedLocaleCompare = function localeCompare(that, locales, options) {
44
+ return (0, exports._PatchedCollator)(locales, options).compare(this, that);
45
+ };
46
+ exports._patchedLocaleCompare = _patchedLocaleCompare;
47
+ exports._patchedLocaleCompare.$original = _localeCompare;
48
+ // ===========================================================================
49
+ // NumberFormat
50
+ // ===========================================================================
51
+ const reformatNumberParts = function (parts) {
52
+ if (!this.mapped) {
53
+ return parts;
54
+ }
55
+ const options = this.super.resolvedOptions();
56
+ if (options.style === 'currency' && options.currencyDisplay === 'symbol') {
57
+ const currency = options.currency;
58
+ if (currency === 'DKK' || currency === 'ISK') {
59
+ parts.forEach((part) => {
60
+ if (part.type === 'currency') {
61
+ part.value = currency === 'DKK' ? 'DKK' : 'kr.';
62
+ }
63
+ });
64
+ }
65
+ }
66
+ return parts;
67
+ };
68
+ const PatchedNumberFormat = function NumberFormat(locales, options) {
69
+ if (!(this instanceof PatchedNumberFormat)) {
70
+ // @ts-expect-error (YOLO! Can't be arsed)
71
+ return new PatchedNumberFormat(locales, options);
72
+ }
73
+ const mappedLocales = mapLocales(locales);
74
+ this.super = _NumberFormat(mappedLocales || locales, options);
75
+ this.mapped = !!mappedLocales;
76
+ };
77
+ // This is all very hacky, but extending the class *AND* preseving the
78
+ // ability to instantiate without `new` is a bit of a pain.
79
+ // Eagerly interested in finding a better way to do this.
80
+ const numberFormatProto = {
81
+ constructor: PatchedNumberFormat,
82
+ format(value) {
83
+ return combineParts(this.formatToParts(value));
84
+ },
85
+ formatRange(value1, value2) {
86
+ return combineParts(this.formatRangeToParts(value1, value2));
87
+ },
88
+ formatToParts(value) {
89
+ return reformatNumberParts.call(this, this.super.formatToParts(value));
90
+ },
91
+ formatRangeToParts(value1, value2) {
92
+ return reformatNumberParts.call(this, this.super.formatRangeToParts(value1, value2));
93
+ },
94
+ resolvedOptions() {
95
+ return this.super.resolvedOptions();
96
+ },
97
+ };
98
+ PatchedNumberFormat.prototype = numberFormatProto;
99
+ // Static methods (not patched since "is" is not ACTUALLY supported.)
100
+ PatchedNumberFormat.supportedLocalesOf = _NumberFormat.supportedLocalesOf;
101
+ PatchedNumberFormat.$original = _NumberFormat;
102
+ exports._PatchedNumberFormat = PatchedNumberFormat;
103
+ // ---------------------------------------------------------------------------
104
+ const _toLocaleString = Number.prototype.toLocaleString;
105
+ const _patchedToLocaleString = function toLocaleString(locales, options) {
106
+ return (0, exports._PatchedNumberFormat)(locales, options).format(this);
107
+ };
108
+ exports._patchedToLocaleString = _patchedToLocaleString;
109
+ exports._patchedToLocaleString.$original = _toLocaleString;
110
+ // ===========================================================================
111
+ // DateTimeFormat
112
+ // ===========================================================================
113
+ const months = {
114
+ jan: 'janúar',
115
+ feb: 'febrúar',
116
+ mar: 'mars',
117
+ apr: 'apríl',
118
+ maj: 'maí',
119
+ jun: 'júní',
120
+ jul: 'júlí',
121
+ aug: 'ágúst',
122
+ // sep: 'september', // is the same
123
+ okt: 'október',
124
+ nov: 'nóvember',
125
+ dec: 'desember',
126
+ };
127
+ const weekdays = {
128
+ man: 'mánudagur',
129
+ tir: 'þriðjudagur',
130
+ ons: 'miðvikudagur',
131
+ tor: 'fimmtudagur',
132
+ fre: 'föstudagur',
133
+ lør: 'laugardagur',
134
+ søn: 'sunnudagur',
135
+ };
136
+ const partMappers = {
137
+ month: (value) => {
138
+ const islMonth = months[value.slice(0, 3)];
139
+ if (islMonth) {
140
+ return value.endsWith('.') ? `${islMonth.slice(0, 3)}.` : islMonth;
141
+ }
142
+ },
143
+ weekday: (value) => {
144
+ const isl = weekdays[value.slice(0, 3)];
145
+ if (isl) {
146
+ return value.endsWith('.') ? `${isl.slice(0, 3)}.` : isl;
147
+ }
148
+ },
149
+ era: (value) => {
150
+ if (!value.endsWith('.')) {
151
+ return value.length === 3
152
+ ? `${value[0]}.k.`
153
+ : value[0] === 'f'
154
+ ? 'fyrir Krist'
155
+ : 'eftir Krist';
156
+ }
157
+ },
158
+ dayPeriod: (value) => {
159
+ return { AM: 'f.h.', PM: 'e.h.' }[value] || value;
160
+ },
161
+ literal: (value, lastType) => {
162
+ if (value === ' den ') {
163
+ return 'inn ';
164
+ }
165
+ else if (value === '.' && (lastType === 'hour' || lastType === 'minute')) {
166
+ return ':';
167
+ }
168
+ },
169
+ };
170
+ const reformatDateTimeParts = function (parts) {
171
+ if (!this.mapped) {
172
+ return parts;
173
+ }
174
+ parts.forEach((part, idx) => {
175
+ var _a;
176
+ const mapper = partMappers[part.type];
177
+ const newValue = mapper && mapper(part.value, (_a = parts[idx - 1]) === null || _a === void 0 ? void 0 : _a.type);
178
+ if (newValue != null) {
179
+ part.value = newValue;
180
+ }
181
+ });
182
+ return parts;
183
+ };
184
+ const PatchedDateTimeFormat = function DateTimeFormat(locales, options) {
185
+ if (!(this instanceof PatchedDateTimeFormat)) {
186
+ // @ts-expect-error (YOLO! Can't be arsed)
187
+ return new PatchedDateTimeFormat(locales, options);
188
+ }
189
+ const mappedLocales = mapLocales(locales);
190
+ if (options === null || options === void 0 ? void 0 : options.hour12) {
191
+ options = {
192
+ ...options,
193
+ hour12: undefined,
194
+ hourCycle: 'h11',
195
+ };
196
+ }
197
+ this.super = _DateTimeFormat(mappedLocales || locales, options);
198
+ this.mapped = !!mappedLocales;
199
+ };
200
+ // This is all very hacky, but extending the class *AND* preseving the
201
+ // ability to instantiate without `new` is a bit of a pain.
202
+ // Eagerly interested in finding a better way to do this.
203
+ const dateTimeFormatProto = {
204
+ constructor: PatchedDateTimeFormat,
205
+ format(value) {
206
+ return combineParts(this.formatToParts(value));
207
+ },
208
+ formatRange(value1, value2) {
209
+ return combineParts(this.formatRangeToParts(value1, value2));
210
+ },
211
+ formatToParts(value) {
212
+ return reformatDateTimeParts.call(this, this.super.formatToParts(value));
213
+ },
214
+ formatRangeToParts(value1, value2) {
215
+ return reformatDateTimeParts.call(this, this.super.formatRangeToParts(value1, value2));
216
+ },
217
+ resolvedOptions() {
218
+ return this.super.resolvedOptions();
219
+ },
220
+ };
221
+ PatchedDateTimeFormat.prototype = dateTimeFormatProto;
222
+ // Static methods (not patched since "is" is not ACTUALLY supported.)
223
+ PatchedDateTimeFormat.supportedLocalesOf = _DateTimeFormat.supportedLocalesOf;
224
+ PatchedDateTimeFormat.$original = _DateTimeFormat;
225
+ exports._PatchedDateTimeFormat = PatchedDateTimeFormat;
226
+ // ---------------------------------------------------------------------------
227
+ const _toLocaleDateString = Date.prototype.toLocaleDateString;
228
+ const _patchedToLocaleDateString = function toLocaleDateString(locales, options) {
229
+ return (0, exports._PatchedDateTimeFormat)(locales, options).format(this);
230
+ };
231
+ exports._patchedToLocaleDateString = _patchedToLocaleDateString;
232
+ exports._patchedToLocaleDateString.$original = _toLocaleDateString;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@reykjavik/webtools",
3
- "version": "0.1.14",
3
+ "version": "0.1.16",
4
4
  "description": "Misc. JS/TS helpers used by Reykjavík City's web dev teams.",
5
5
  "main": "index.js",
6
6
  "repository": "ssh://git@github.com:reykjavikcity/webtools.git",