@reykjavik/webtools 0.1.13 → 0.1.15

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,11 +4,20 @@
4
4
 
5
5
  - ... <!-- Add new lines here. -->
6
6
 
7
- ## 0.1.13
7
+ ## 0.1.15
8
+
9
+ _2024-03-08_
10
+
11
+ - `@reykjavik/webtools/fixIcelandicLocale`:
12
+ - feat: Patch `Intl.Collator`
13
+ - feat: Patch `Intl.NumberFormat` and `Number.prototype.toLocaleString`
14
+ - feat: Patch `Intl.DateTimeFormat` and `Date.prototype.toLocaleDateString`
15
+
16
+ ## 0.1.13 – 0.1.14
8
17
 
9
18
  _2024-03-06_
10
19
 
11
- - feat: Add `@reykjavik/webtools/fixIcelandicLocale` polyfill module
20
+ - feat: Add `@reykjavik/webtools/fixIcelandicLocale` Chrome paching module
12
21
 
13
22
  ## 0.1.12
14
23
 
@@ -28,7 +37,7 @@ _2024-02-14_
28
37
 
29
38
  _2024-02-14_
30
39
 
31
- - `@reykjavik/webtools/http`
40
+ - `@reykjavik/webtools/http`:
32
41
  - feat: Add the rest of the more obscure HTTP status constants (WebDAV,
33
42
  etc.)
34
43
  - fix: Add `429`, `432`, `451` to `HTTP_CLIENT_ERROR_ALL` type
@@ -37,7 +46,7 @@ _2024-02-14_
37
46
 
38
47
  _2023-04-27_
39
48
 
40
- - `@reykjavik/webtools/http`
49
+ - `@reykjavik/webtools/http`:
41
50
  - feat: Add `toSec` TTL helper
42
51
 
43
52
  ## 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,60 @@ 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.NumzberFormat` 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 timezone will appear in Danish
482
+ - The `dayPeriod` option is not supported and prints the day-period in Danish.
483
+ - Custom formatted `DD.MM.YY` (2-digit year) dates turn into time-like
484
+ `DD:MM:YY` strings.
445
485
 
446
486
  ---
447
487
 
@@ -1,21 +1,14 @@
1
- const locAliases = {
2
- // Danish is good enough substitution for Icelandic
3
- is: 'da',
4
- 'is-is': 'da',
5
- };
6
- const mapLocales = (locales) => {
7
- locales = typeof locales === 'string' ? [locales] : locales || [];
8
- return locales.map((loc) => locAliases[loc.toLowerCase()] || loc);
9
- };
10
- // ---------------------------------------------------------------------------
11
- if ( /*#__PURE__*/'ö'.localeCompare('p', 'is') < 0) {
12
- const _localeCompare = String.prototype.localeCompare;
13
- /**
14
- * Polyfill for String.prototype.localeCompare for the 'is' locale
15
- * in browsers that don't support it (\*cough* Chrome \*cough*).
16
- */
17
- String.prototype.localeCompare = function (that, locales, options) {
18
- return _localeCompare.call(this, that, mapLocales(locales), options);
19
- };
1
+ import { _PatchedCollator, _PatchedDateTimeFormat, _patchedLocaleCompare, _PatchedNumberFormat, _patchedToLocaleDateString, _patchedToLocaleString, } from './fixIcelandicLocale.privates.js';
2
+ /*
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...
6
+ */
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;
20
14
  }
21
- 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,248 @@
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
+ // ===========================================================================
23
+ // Collator
24
+ // ===========================================================================
25
+ const PatchedCollator = function Collator(locales, options) {
26
+ locales = mapLocales(locales) || locales;
27
+ const instance = new _Collator(locales, options);
28
+ Object.setPrototypeOf(instance, PatchedCollator.prototype);
29
+ return instance;
30
+ };
31
+ PatchedCollator.prototype = Object.create(_Collator.prototype);
32
+ PatchedCollator.prototype.constructor = PatchedCollator;
33
+ // Static methods (not patched since "is" is not ACTUALLY supported.)
34
+ PatchedCollator.supportedLocalesOf = _Collator.supportedLocalesOf;
35
+ PatchedCollator.$original = _Collator;
36
+ export const _PatchedCollator = PatchedCollator;
37
+ // ---------------------------------------------------------------------------
38
+ const _localeCompare = String.prototype.localeCompare;
39
+ export const _patchedLocaleCompare = function localeCompare(that, locales, options) {
40
+ return _PatchedCollator(locales, options).compare(this, that);
41
+ };
42
+ _patchedLocaleCompare.$original = _localeCompare;
43
+ // ===========================================================================
44
+ // NumberFormat
45
+ // ===========================================================================
46
+ const reformatNumber = function (result) {
47
+ if (!this.mapped) {
48
+ return result;
49
+ }
50
+ const options = this.super.resolvedOptions();
51
+ if (options.style === 'currency' && options.currencyDisplay === 'symbol') {
52
+ if (options.currency === 'DKK') {
53
+ return result.replace(/kr\./g, 'DKK');
54
+ }
55
+ if (options.currency === 'ISK') {
56
+ return result.replace(/ISK/g, 'kr.');
57
+ }
58
+ }
59
+ return result;
60
+ };
61
+ const reformatNumberParts = function (parts) {
62
+ if (!this.mapped) {
63
+ return parts;
64
+ }
65
+ const options = this.super.resolvedOptions();
66
+ if (options.style === 'currency' && options.currencyDisplay === 'symbol') {
67
+ const currency = options.currency;
68
+ if (currency === 'DKK' || currency === 'ISK') {
69
+ parts.forEach((part) => {
70
+ if (part.type === 'currency') {
71
+ part.value = currency === 'DKK' ? 'DKK' : 'kr.';
72
+ }
73
+ });
74
+ }
75
+ }
76
+ return parts;
77
+ };
78
+ const PatchedNumberFormat = function NumberFormat(locales, options) {
79
+ if (!(this instanceof PatchedNumberFormat)) {
80
+ // @ts-expect-error (YOLO! Can't be arsed)
81
+ return new PatchedNumberFormat(locales, options);
82
+ }
83
+ const mappedLocales = mapLocales(locales);
84
+ this.super = _NumberFormat(mappedLocales || locales, options);
85
+ this.mapped = !!mappedLocales;
86
+ };
87
+ // This is all very hacky, but extending the class *AND* preseving the
88
+ // ability to instantiate without `new` is a bit of a pain.
89
+ // Eagerly interested in finding a better way to do this.
90
+ const numberFormatProto = {
91
+ constructor: PatchedNumberFormat,
92
+ format(value) {
93
+ return reformatNumber.call(this, this.super.format(value));
94
+ },
95
+ formatRange(value1, value2) {
96
+ return reformatNumber.call(this, this.super.formatRange(value1, value2));
97
+ },
98
+ formatToParts(value) {
99
+ return reformatNumberParts.call(this, this.super.formatToParts(value));
100
+ },
101
+ formatRangeToParts(value1, value2) {
102
+ return reformatNumberParts.call(this, this.super.formatRangeToParts(value1, value2));
103
+ },
104
+ resolvedOptions() {
105
+ return this.super.resolvedOptions();
106
+ },
107
+ };
108
+ PatchedNumberFormat.prototype = numberFormatProto;
109
+ // Static methods (not patched since "is" is not ACTUALLY supported.)
110
+ PatchedNumberFormat.supportedLocalesOf = _NumberFormat.supportedLocalesOf;
111
+ PatchedNumberFormat.$original = _NumberFormat;
112
+ export const _PatchedNumberFormat = PatchedNumberFormat;
113
+ // ---------------------------------------------------------------------------
114
+ const _toLocaleString = Number.prototype.toLocaleString;
115
+ export const _patchedToLocaleString = function toLocaleString(locales, options) {
116
+ return _PatchedNumberFormat(locales, options).format(this);
117
+ };
118
+ _patchedToLocaleString.$original = _toLocaleString;
119
+ // ===========================================================================
120
+ // DateTimeFormat
121
+ // ===========================================================================
122
+ const months = [
123
+ ['januar', 'janúar', true],
124
+ ['februar', 'febrúar'],
125
+ ['marts', 'mars'],
126
+ ['april', 'apríl'],
127
+ ['maj', 'maí'],
128
+ ['juni', 'júní', true],
129
+ ['juli', 'júlí', true],
130
+ ['august', 'ágúst', true],
131
+ // ['september', 'september'],
132
+ ['oktober', 'október'],
133
+ ['november', 'nóvember', true],
134
+ ['december', 'desember', true],
135
+ ];
136
+ const weekdays = [
137
+ ['mandag', 'mánudagur'],
138
+ ['tirsdag', 'þriðjudagur', 4],
139
+ ['onsdag', 'miðvikudagur'],
140
+ ['torsdag', 'fimmtudagur', 4],
141
+ ['fredag', 'föstudagur'],
142
+ ['lørdag', 'laugardagur'],
143
+ ['søndag', 'sunnudagur'],
144
+ ];
145
+ const reformatDateTime = function (result) {
146
+ if (!this.mapped) {
147
+ return result;
148
+ }
149
+ const options = this.super.resolvedOptions();
150
+ let mappedResult = result;
151
+ let monthMatches = 0;
152
+ for (let i = 0, month; (month = months[i]); i++) {
153
+ const [da, is, checkShort] = month;
154
+ mappedResult = mappedResult.replace(da, is);
155
+ if (checkShort && mappedResult === result) {
156
+ mappedResult = mappedResult.replace(da.slice(0, 3), is.slice(0, 3));
157
+ }
158
+ if (mappedResult !== result) {
159
+ monthMatches++;
160
+ if (monthMatches >= 2) {
161
+ break;
162
+ }
163
+ }
164
+ }
165
+ result = mappedResult; // reset result
166
+ let weekdayMatches = 0;
167
+ for (let i = 0, weekday; (weekday = weekdays[i]); i++) {
168
+ const [da, is, shortLength] = weekday;
169
+ mappedResult = mappedResult.replace(da, is);
170
+ if (mappedResult === result) {
171
+ mappedResult = mappedResult.replace(da.slice(0, shortLength || 3), is.slice(0, 3));
172
+ }
173
+ if (mappedResult !== result) {
174
+ weekdayMatches++;
175
+ if (weekdayMatches >= 2) {
176
+ break;
177
+ }
178
+ }
179
+ }
180
+ result = mappedResult;
181
+ if (/Kristus/.test(result)) {
182
+ result = result.replace(/før Kristus/g, 'fyrir Krist');
183
+ result = result.replace(/efter Kristus/g, 'eftir Krist');
184
+ }
185
+ result = result.replace(/(f|e)Kr/g, '$1.k.');
186
+ result = result.replace(/AM/g, 'f.h.');
187
+ result = result.replace(/PM/g, 'e.h.');
188
+ // convert timestamps from `00.00` to `00:00`
189
+ result = result.replace(/(?:^|\s)\d\d\.\d\d(?:\.\d\d)?(?:,|\s|$)/g, (match) => match.replace(/\./g, ':'));
190
+ result = result.replace(/ den/g, 'inn');
191
+ return result;
192
+ };
193
+ const reformatDateTimeParts = function (parts) {
194
+ if (!this.mapped) {
195
+ return parts;
196
+ }
197
+ const options = this.super.resolvedOptions();
198
+ // reformat
199
+ return parts;
200
+ };
201
+ const PatchedDateTimeFormat = function DateTimeFormat(locales, options) {
202
+ if (!(this instanceof PatchedDateTimeFormat)) {
203
+ // @ts-expect-error (YOLO! Can't be arsed)
204
+ return new PatchedDateTimeFormat(locales, options);
205
+ }
206
+ const mappedLocales = mapLocales(locales);
207
+ if (options === null || options === void 0 ? void 0 : options.hour12) {
208
+ options = {
209
+ ...options,
210
+ hour12: undefined,
211
+ hourCycle: 'h11',
212
+ };
213
+ }
214
+ this.super = _DateTimeFormat(mappedLocales || locales, options);
215
+ this.mapped = !!mappedLocales;
216
+ };
217
+ // This is all very hacky, but extending the class *AND* preseving the
218
+ // ability to instantiate without `new` is a bit of a pain.
219
+ // Eagerly interested in finding a better way to do this.
220
+ const dateTimeFormatProto = {
221
+ constructor: PatchedDateTimeFormat,
222
+ format(value) {
223
+ return reformatDateTime.call(this, this.super.format(value));
224
+ },
225
+ formatRange(value1, value2) {
226
+ return reformatDateTime.call(this, this.super.formatRange(value1, value2));
227
+ },
228
+ formatToParts(value) {
229
+ return reformatDateTimeParts.call(this, this.super.formatToParts(value));
230
+ },
231
+ formatRangeToParts(value1, value2) {
232
+ return reformatDateTimeParts.call(this, this.super.formatRangeToParts(value1, value2));
233
+ },
234
+ resolvedOptions() {
235
+ return this.super.resolvedOptions();
236
+ },
237
+ };
238
+ PatchedDateTimeFormat.prototype = dateTimeFormatProto;
239
+ // Static methods (not patched since "is" is not ACTUALLY supported.)
240
+ PatchedDateTimeFormat.supportedLocalesOf = _DateTimeFormat.supportedLocalesOf;
241
+ PatchedDateTimeFormat.$original = _DateTimeFormat;
242
+ export const _PatchedDateTimeFormat = PatchedDateTimeFormat;
243
+ // ---------------------------------------------------------------------------
244
+ const _toLocaleDateString = Date.prototype.toLocaleDateString;
245
+ export const _patchedToLocaleDateString = function toLocaleDateString(locales, options) {
246
+ return _PatchedDateTimeFormat(locales, options).format(this);
247
+ };
248
+ _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,21 +1,16 @@
1
1
  "use strict";
2
- const locAliases = {
3
- // Danish is good enough substitution for Icelandic
4
- is: 'da',
5
- 'is-is': 'da',
6
- };
7
- const mapLocales = (locales) => {
8
- locales = typeof locales === 'string' ? [locales] : locales || [];
9
- return locales.map((loc) => locAliases[loc.toLowerCase()] || loc);
10
- };
11
- // ---------------------------------------------------------------------------
12
- if ( /*#__PURE__*/'ö'.localeCompare('p', 'is') < 0) {
13
- const _localeCompare = String.prototype.localeCompare;
14
- /**
15
- * Polyfill for String.prototype.localeCompare for the 'is' locale
16
- * in browsers that don't support it (\*cough* Chrome \*cough*).
17
- */
18
- String.prototype.localeCompare = function (that, locales, options) {
19
- return _localeCompare.call(this, that, mapLocales(locales), options);
20
- };
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ const fixIcelandicLocale_privates_js_1 = require("./fixIcelandicLocale.privates.js");
4
+ /*
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...
8
+ */
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;
21
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,254 @@
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
+ // ===========================================================================
26
+ // Collator
27
+ // ===========================================================================
28
+ const PatchedCollator = function Collator(locales, options) {
29
+ locales = mapLocales(locales) || locales;
30
+ const instance = new _Collator(locales, options);
31
+ Object.setPrototypeOf(instance, PatchedCollator.prototype);
32
+ return instance;
33
+ };
34
+ PatchedCollator.prototype = Object.create(_Collator.prototype);
35
+ PatchedCollator.prototype.constructor = PatchedCollator;
36
+ // Static methods (not patched since "is" is not ACTUALLY supported.)
37
+ PatchedCollator.supportedLocalesOf = _Collator.supportedLocalesOf;
38
+ PatchedCollator.$original = _Collator;
39
+ exports._PatchedCollator = PatchedCollator;
40
+ // ---------------------------------------------------------------------------
41
+ const _localeCompare = String.prototype.localeCompare;
42
+ const _patchedLocaleCompare = function localeCompare(that, locales, options) {
43
+ return (0, exports._PatchedCollator)(locales, options).compare(this, that);
44
+ };
45
+ exports._patchedLocaleCompare = _patchedLocaleCompare;
46
+ exports._patchedLocaleCompare.$original = _localeCompare;
47
+ // ===========================================================================
48
+ // NumberFormat
49
+ // ===========================================================================
50
+ const reformatNumber = function (result) {
51
+ if (!this.mapped) {
52
+ return result;
53
+ }
54
+ const options = this.super.resolvedOptions();
55
+ if (options.style === 'currency' && options.currencyDisplay === 'symbol') {
56
+ if (options.currency === 'DKK') {
57
+ return result.replace(/kr\./g, 'DKK');
58
+ }
59
+ if (options.currency === 'ISK') {
60
+ return result.replace(/ISK/g, 'kr.');
61
+ }
62
+ }
63
+ return result;
64
+ };
65
+ const reformatNumberParts = function (parts) {
66
+ if (!this.mapped) {
67
+ return parts;
68
+ }
69
+ const options = this.super.resolvedOptions();
70
+ if (options.style === 'currency' && options.currencyDisplay === 'symbol') {
71
+ const currency = options.currency;
72
+ if (currency === 'DKK' || currency === 'ISK') {
73
+ parts.forEach((part) => {
74
+ if (part.type === 'currency') {
75
+ part.value = currency === 'DKK' ? 'DKK' : 'kr.';
76
+ }
77
+ });
78
+ }
79
+ }
80
+ return parts;
81
+ };
82
+ const PatchedNumberFormat = function NumberFormat(locales, options) {
83
+ if (!(this instanceof PatchedNumberFormat)) {
84
+ // @ts-expect-error (YOLO! Can't be arsed)
85
+ return new PatchedNumberFormat(locales, options);
86
+ }
87
+ const mappedLocales = mapLocales(locales);
88
+ this.super = _NumberFormat(mappedLocales || locales, options);
89
+ this.mapped = !!mappedLocales;
90
+ };
91
+ // This is all very hacky, but extending the class *AND* preseving the
92
+ // ability to instantiate without `new` is a bit of a pain.
93
+ // Eagerly interested in finding a better way to do this.
94
+ const numberFormatProto = {
95
+ constructor: PatchedNumberFormat,
96
+ format(value) {
97
+ return reformatNumber.call(this, this.super.format(value));
98
+ },
99
+ formatRange(value1, value2) {
100
+ return reformatNumber.call(this, this.super.formatRange(value1, value2));
101
+ },
102
+ formatToParts(value) {
103
+ return reformatNumberParts.call(this, this.super.formatToParts(value));
104
+ },
105
+ formatRangeToParts(value1, value2) {
106
+ return reformatNumberParts.call(this, this.super.formatRangeToParts(value1, value2));
107
+ },
108
+ resolvedOptions() {
109
+ return this.super.resolvedOptions();
110
+ },
111
+ };
112
+ PatchedNumberFormat.prototype = numberFormatProto;
113
+ // Static methods (not patched since "is" is not ACTUALLY supported.)
114
+ PatchedNumberFormat.supportedLocalesOf = _NumberFormat.supportedLocalesOf;
115
+ PatchedNumberFormat.$original = _NumberFormat;
116
+ exports._PatchedNumberFormat = PatchedNumberFormat;
117
+ // ---------------------------------------------------------------------------
118
+ const _toLocaleString = Number.prototype.toLocaleString;
119
+ const _patchedToLocaleString = function toLocaleString(locales, options) {
120
+ return (0, exports._PatchedNumberFormat)(locales, options).format(this);
121
+ };
122
+ exports._patchedToLocaleString = _patchedToLocaleString;
123
+ exports._patchedToLocaleString.$original = _toLocaleString;
124
+ // ===========================================================================
125
+ // DateTimeFormat
126
+ // ===========================================================================
127
+ const months = [
128
+ ['januar', 'janúar', true],
129
+ ['februar', 'febrúar'],
130
+ ['marts', 'mars'],
131
+ ['april', 'apríl'],
132
+ ['maj', 'maí'],
133
+ ['juni', 'júní', true],
134
+ ['juli', 'júlí', true],
135
+ ['august', 'ágúst', true],
136
+ // ['september', 'september'],
137
+ ['oktober', 'október'],
138
+ ['november', 'nóvember', true],
139
+ ['december', 'desember', true],
140
+ ];
141
+ const weekdays = [
142
+ ['mandag', 'mánudagur'],
143
+ ['tirsdag', 'þriðjudagur', 4],
144
+ ['onsdag', 'miðvikudagur'],
145
+ ['torsdag', 'fimmtudagur', 4],
146
+ ['fredag', 'föstudagur'],
147
+ ['lørdag', 'laugardagur'],
148
+ ['søndag', 'sunnudagur'],
149
+ ];
150
+ const reformatDateTime = function (result) {
151
+ if (!this.mapped) {
152
+ return result;
153
+ }
154
+ const options = this.super.resolvedOptions();
155
+ let mappedResult = result;
156
+ let monthMatches = 0;
157
+ for (let i = 0, month; (month = months[i]); i++) {
158
+ const [da, is, checkShort] = month;
159
+ mappedResult = mappedResult.replace(da, is);
160
+ if (checkShort && mappedResult === result) {
161
+ mappedResult = mappedResult.replace(da.slice(0, 3), is.slice(0, 3));
162
+ }
163
+ if (mappedResult !== result) {
164
+ monthMatches++;
165
+ if (monthMatches >= 2) {
166
+ break;
167
+ }
168
+ }
169
+ }
170
+ result = mappedResult; // reset result
171
+ let weekdayMatches = 0;
172
+ for (let i = 0, weekday; (weekday = weekdays[i]); i++) {
173
+ const [da, is, shortLength] = weekday;
174
+ mappedResult = mappedResult.replace(da, is);
175
+ if (mappedResult === result) {
176
+ mappedResult = mappedResult.replace(da.slice(0, shortLength || 3), is.slice(0, 3));
177
+ }
178
+ if (mappedResult !== result) {
179
+ weekdayMatches++;
180
+ if (weekdayMatches >= 2) {
181
+ break;
182
+ }
183
+ }
184
+ }
185
+ result = mappedResult;
186
+ if (/Kristus/.test(result)) {
187
+ result = result.replace(/før Kristus/g, 'fyrir Krist');
188
+ result = result.replace(/efter Kristus/g, 'eftir Krist');
189
+ }
190
+ result = result.replace(/(f|e)Kr/g, '$1.k.');
191
+ result = result.replace(/AM/g, 'f.h.');
192
+ result = result.replace(/PM/g, 'e.h.');
193
+ // convert timestamps from `00.00` to `00:00`
194
+ result = result.replace(/(?:^|\s)\d\d\.\d\d(?:\.\d\d)?(?:,|\s|$)/g, (match) => match.replace(/\./g, ':'));
195
+ result = result.replace(/ den/g, 'inn');
196
+ return result;
197
+ };
198
+ const reformatDateTimeParts = function (parts) {
199
+ if (!this.mapped) {
200
+ return parts;
201
+ }
202
+ const options = this.super.resolvedOptions();
203
+ // reformat
204
+ return parts;
205
+ };
206
+ const PatchedDateTimeFormat = function DateTimeFormat(locales, options) {
207
+ if (!(this instanceof PatchedDateTimeFormat)) {
208
+ // @ts-expect-error (YOLO! Can't be arsed)
209
+ return new PatchedDateTimeFormat(locales, options);
210
+ }
211
+ const mappedLocales = mapLocales(locales);
212
+ if (options === null || options === void 0 ? void 0 : options.hour12) {
213
+ options = {
214
+ ...options,
215
+ hour12: undefined,
216
+ hourCycle: 'h11',
217
+ };
218
+ }
219
+ this.super = _DateTimeFormat(mappedLocales || locales, options);
220
+ this.mapped = !!mappedLocales;
221
+ };
222
+ // This is all very hacky, but extending the class *AND* preseving the
223
+ // ability to instantiate without `new` is a bit of a pain.
224
+ // Eagerly interested in finding a better way to do this.
225
+ const dateTimeFormatProto = {
226
+ constructor: PatchedDateTimeFormat,
227
+ format(value) {
228
+ return reformatDateTime.call(this, this.super.format(value));
229
+ },
230
+ formatRange(value1, value2) {
231
+ return reformatDateTime.call(this, this.super.formatRange(value1, value2));
232
+ },
233
+ formatToParts(value) {
234
+ return reformatDateTimeParts.call(this, this.super.formatToParts(value));
235
+ },
236
+ formatRangeToParts(value1, value2) {
237
+ return reformatDateTimeParts.call(this, this.super.formatRangeToParts(value1, value2));
238
+ },
239
+ resolvedOptions() {
240
+ return this.super.resolvedOptions();
241
+ },
242
+ };
243
+ PatchedDateTimeFormat.prototype = dateTimeFormatProto;
244
+ // Static methods (not patched since "is" is not ACTUALLY supported.)
245
+ PatchedDateTimeFormat.supportedLocalesOf = _DateTimeFormat.supportedLocalesOf;
246
+ PatchedDateTimeFormat.$original = _DateTimeFormat;
247
+ exports._PatchedDateTimeFormat = PatchedDateTimeFormat;
248
+ // ---------------------------------------------------------------------------
249
+ const _toLocaleDateString = Date.prototype.toLocaleDateString;
250
+ const _patchedToLocaleDateString = function toLocaleDateString(locales, options) {
251
+ return (0, exports._PatchedDateTimeFormat)(locales, options).format(this);
252
+ };
253
+ exports._patchedToLocaleDateString = _patchedToLocaleDateString;
254
+ exports._patchedToLocaleDateString.$original = _toLocaleDateString;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@reykjavik/webtools",
3
- "version": "0.1.13",
3
+ "version": "0.1.15",
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",
@@ -36,7 +36,9 @@
36
36
  "engines": {
37
37
  "node": ">=16"
38
38
  },
39
- "sideEffects": false,
39
+ "sideEffects": [
40
+ "**/fixIcelandicLocale.js"
41
+ ],
40
42
  "exports": {
41
43
  ".": {
42
44
  "import": "./esm/index.js",