@reykjavik/webtools 0.1.17 → 0.1.19

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,14 +4,24 @@
4
4
 
5
5
  - ... <!-- Add new lines here. -->
6
6
 
7
- ## 0.1.17
7
+ ## 0.1.19
8
8
 
9
- _2024-03-09_
9
+ _2024-03-11_
10
+
11
+ - fix: Incorrect alphabetization of accented characters as part of a word …
12
+ (not just a single character) This fix corrects the sorting of initial
13
+ letters, but characters inside the string stay mixed in with their
14
+ unaccented base character.
15
+ - fix: Incorrect `Intl.ListFormat` format in narrow+unit mode
16
+
17
+ ## 0.1.18
18
+
19
+ _2024-03-11_
10
20
 
11
21
  - `@reykjavik/webtools/fixIcelandicLocale`:
12
- - feat: Add support for `DateTimeFormat`'s `dayPeriod` option
22
+ - feat: Patch `Intl.PluralRules` and `Intl.ListFormat`
13
23
 
14
- ## 0.1.16
24
+ ## 0.1.16 – 0.1.17
15
25
 
16
26
  _2024-03-09_
17
27
 
package/README.md CHANGED
@@ -439,6 +439,8 @@ to their return values.
439
439
  - `Intl.Collator` and `String.prototype.localeCompare`
440
440
  - `Intl.NumberFormat` and `Number.prototype.toLocaleString`
441
441
  - `Intl.DateTimeFormat` and `Date.prototype.toLocaleDateString`
442
+ - `Intl.PluralRules`
443
+ - `Intl.ListFormat`
442
444
 
443
445
  This provides usable (but not perfect) results, with some caveats listed
444
446
  below.
@@ -449,7 +451,7 @@ your app's entry point:
449
451
  ```ts
450
452
  import '@reykjavik/webtools/fixIcelandicLocale';
451
453
 
452
- // Then continue with your day and use `localeCompare` and Intl.Collator,
454
+ // Then continue with your day and use `localeCompare` and other Intl.* methods
453
455
  // as you normally would. (See "limitations" below.)
454
456
  ```
455
457
 
@@ -460,10 +462,11 @@ detection test.)
460
462
 
461
463
  **`Intl.Collator` and `localeCompare`:**
462
464
 
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.
465
+ - It incorrectly treats `ð` and `d` as the same letter (most of the time), and
466
+ the acute-accented characters `á`, `é`, `í`, `ó`, `ú` and `ý` get lumped in
467
+ with their non-accented counterparts (unless the compared).
468
+ We fix this only for the first letter in the string, but not for the rest of
469
+ it.
467
470
 
468
471
  **`Intl.NumberFormat` and `toLocaleString`:**
469
472
 
@@ -1,4 +1,4 @@
1
- import { _PatchedCollator, _PatchedDateTimeFormat, _patchedLocaleCompare, _PatchedNumberFormat, _patchedToLocaleDateString, _patchedToLocaleString, } from './fixIcelandicLocale.privates.js';
1
+ import { _PatchedCollator, _PatchedDateTimeFormat, _PatchedListFormat, _patchedLocaleCompare, _PatchedNumberFormat, _PatchedPluralRules, _patchedToLocaleDateString, _patchedToLocaleString, } from './fixIcelandicLocale.privates.js';
2
2
  /*
3
3
  Mantra: Partial Icelandic suppoort is better than none. Partial Icelandic
4
4
  suppoort is better than none. Partial Icelandic suppoort is better than
@@ -12,3 +12,11 @@ if (Intl.Collator.supportedLocalesOf(['is']).length < 1) {
12
12
  Intl.DateTimeFormat = _PatchedDateTimeFormat;
13
13
  Date.prototype.toLocaleDateString = _patchedToLocaleDateString;
14
14
  }
15
+ /* eslint-disable @typescript-eslint/no-unnecessary-type-assertion, @typescript-eslint/no-unnecessary-condition */
16
+ if (Intl.ListFormat && Intl.ListFormat.supportedLocalesOf(['is']).length < 1) {
17
+ Intl.ListFormat = _PatchedListFormat;
18
+ }
19
+ if (Intl.PluralRules && Intl.PluralRules.supportedLocalesOf(['is']).length < 1) {
20
+ Intl.PluralRules = _PatchedPluralRules;
21
+ }
22
+ /* eslint-enable @typescript-eslint/no-unnecessary-type-assertion, @typescript-eslint/no-unnecessary-condition */
@@ -43,3 +43,19 @@ export declare const _patchedToLocaleDateString: {
43
43
  (locales?: Intl.LocalesArgument, options?: Intl.DateTimeFormatOptions | undefined): string;
44
44
  };
45
45
  };
46
+ export declare const _PatchedPluralRules: {
47
+ (locales?: string | string[] | undefined, options?: Intl.PluralRulesOptions | undefined): Intl.PluralRules;
48
+ new (locales?: string | string[] | undefined, options?: Intl.PluralRulesOptions | undefined): Intl.PluralRules;
49
+ supportedLocalesOf(locales: string | string[], options?: {
50
+ localeMatcher?: "best fit" | "lookup" | undefined;
51
+ } | undefined): string[];
52
+ } & {
53
+ $original: typeof Intl.PluralRules;
54
+ };
55
+ export declare const _PatchedListFormat: {
56
+ new (locales?: string | string[] | undefined, options?: Intl.ListFormatOptions | undefined): Intl.ListFormat;
57
+ prototype: Intl.ListFormat;
58
+ supportedLocalesOf(locales: string | string[], options?: Pick<Intl.ListFormatOptions, "localeMatcher"> | undefined): string[];
59
+ } & {
60
+ $original: typeof Intl.ListFormat;
61
+ };
@@ -1,6 +1,5 @@
1
+ var _a, _b;
1
2
  const _Collator = Intl.Collator;
2
- const _NumberFormat = Intl.NumberFormat;
3
- const _DateTimeFormat = Intl.DateTimeFormat;
4
3
  const mapLocales = (locales) => {
5
4
  locales = typeof locales === 'string' ? [locales] : locales || [];
6
5
  for (let i = 0, loc; (loc = locales[i]); i++) {
@@ -12,6 +11,8 @@ const mapLocales = (locales) => {
12
11
  // or "accent" then `ð` is consiered a variant of `d` and the letters
13
12
  // á, é, í, ó, ú, and ý are not treated as separate letters but simply
14
13
  // variants of a, e, i, o, u, and y.
14
+ // Also when the accented characters are part of a word, they are treated
15
+ // as fully equal to the base letter.
15
16
  return ['da'];
16
17
  }
17
18
  if (_Collator.supportedLocalesOf(loc).length) {
@@ -20,17 +21,35 @@ const mapLocales = (locales) => {
20
21
  }
21
22
  };
22
23
  const combineParts = (parts) => parts.map(({ value }) => value).join('');
23
- // ===========================================================================
24
- // Collator
25
- // ===========================================================================
26
24
  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;
25
+ if (!(this instanceof PatchedCollator)) {
26
+ // @ts-expect-error (YOLO! Can't be arsed)
27
+ return new PatchedCollator(locales, options);
28
+ }
29
+ const mappedLocales = mapLocales(locales);
30
+ this.super = _Collator(mappedLocales || locales, options);
31
+ this.mapped = !!mappedLocales;
32
+ };
33
+ // This is all very hacky, but extending the class *AND* preseving the
34
+ // ability to instantiate without `new` is a bit of a pain.
35
+ // Eagerly interested in finding a better way to do this.
36
+ const CollatorProto = {
37
+ constructor: PatchedCollator,
38
+ compare(a, b) {
39
+ const res1 = this.super.compare(a, b);
40
+ const a0 = a.charAt(0);
41
+ const b0 = b.charAt(0);
42
+ if (/\d/.test(a0 + b0)) {
43
+ return res1;
44
+ }
45
+ const res2 = this.super.compare(a0, b0);
46
+ return res2 !== 0 ? res2 : res1;
47
+ },
48
+ resolvedOptions() {
49
+ return this.super.resolvedOptions();
50
+ },
31
51
  };
32
- PatchedCollator.prototype = Object.create(_Collator.prototype);
33
- PatchedCollator.prototype.constructor = PatchedCollator;
52
+ PatchedCollator.prototype = CollatorProto;
34
53
  // Static methods (not patched since "is" is not ACTUALLY supported.)
35
54
  PatchedCollator.supportedLocalesOf = _Collator.supportedLocalesOf;
36
55
  PatchedCollator.$original = _Collator;
@@ -44,6 +63,7 @@ _patchedLocaleCompare.$original = _localeCompare;
44
63
  // ===========================================================================
45
64
  // NumberFormat
46
65
  // ===========================================================================
66
+ const _NumberFormat = Intl.NumberFormat;
47
67
  const reformatNumberParts = function (parts) {
48
68
  if (!this.mapped) {
49
69
  return parts;
@@ -192,6 +212,7 @@ const reformatDateTimeParts = function (parts) {
192
212
  });
193
213
  return parts;
194
214
  };
215
+ const _DateTimeFormat = Intl.DateTimeFormat;
195
216
  const PatchedDateTimeFormat = function DateTimeFormat(locales, options) {
196
217
  if (!(this instanceof PatchedDateTimeFormat)) {
197
218
  // @ts-expect-error (YOLO! Can't be arsed)
@@ -240,3 +261,73 @@ export const _patchedToLocaleDateString = function toLocaleDateString(locales, o
240
261
  return _PatchedDateTimeFormat(locales, options).format(this);
241
262
  };
242
263
  _patchedToLocaleDateString.$original = _toLocaleDateString;
264
+ // ===========================================================================
265
+ // PluralRules
266
+ // ===========================================================================
267
+ const _PluralRules = Intl.PluralRules;
268
+ let PatchedPluralRules;
269
+ // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition
270
+ if (_PluralRules) {
271
+ PatchedPluralRules = (_a = class PluralRules extends _PluralRules {
272
+ pluralIsl(n) {
273
+ return this.ord ? 'other' : n % 10 !== 1 || n % 100 === 11 ? 'other' : 'one';
274
+ }
275
+ constructor(locales, options) {
276
+ const mappedLocales = mapLocales(locales);
277
+ super(mappedLocales || locales, options);
278
+ this.mapped = !!mappedLocales;
279
+ this.ord = (options === null || options === void 0 ? void 0 : options.type) === 'ordinal';
280
+ }
281
+ select(n) {
282
+ if (this.mapped) {
283
+ // Pluralization function for Icelandic
284
+ // Copied over from https://www.npmjs.com/package/translate.js
285
+ return this.pluralIsl(n);
286
+ }
287
+ return super.select(n);
288
+ }
289
+ selectRange(n, n2) {
290
+ if (this.mapped) {
291
+ return this.pluralIsl(n2);
292
+ }
293
+ // @ts-expect-error (TS doesn't know about the .selectRange() method ...yet?)
294
+ return super.selectRange(n, n2);
295
+ }
296
+ },
297
+ _a.$original = _PluralRules,
298
+ _a);
299
+ }
300
+ export const _PatchedPluralRules = PatchedPluralRules;
301
+ // ===========================================================================
302
+ // ListFormat
303
+ // ===========================================================================
304
+ const _ListFormat = Intl.ListFormat;
305
+ let PatchedListFormat;
306
+ // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition
307
+ if (_ListFormat) {
308
+ PatchedListFormat = (_b = class ListFormat extends _ListFormat {
309
+ constructor(locales, options) {
310
+ const mappedLocales = mapLocales(locales);
311
+ super(mappedLocales || locales, options);
312
+ this.mapped = !!mappedLocales;
313
+ }
314
+ format(list) {
315
+ return this.mapped ? combineParts(this.formatToParts(list)) : super.format(list);
316
+ }
317
+ formatToParts(list) {
318
+ const parts = super.formatToParts(list);
319
+ if (this.mapped) {
320
+ for (const item of parts) {
321
+ const { value } = item;
322
+ if (item.type === 'literal' && (value === ' el. ' || value === ' eller ')) {
323
+ item.value = ' eða ';
324
+ }
325
+ }
326
+ }
327
+ return parts;
328
+ }
329
+ },
330
+ _b.$original = _ListFormat,
331
+ _b);
332
+ }
333
+ export const _PatchedListFormat = PatchedListFormat;
@@ -14,3 +14,11 @@ if (Intl.Collator.supportedLocalesOf(['is']).length < 1) {
14
14
  Intl.DateTimeFormat = fixIcelandicLocale_privates_js_1._PatchedDateTimeFormat;
15
15
  Date.prototype.toLocaleDateString = fixIcelandicLocale_privates_js_1._patchedToLocaleDateString;
16
16
  }
17
+ /* eslint-disable @typescript-eslint/no-unnecessary-type-assertion, @typescript-eslint/no-unnecessary-condition */
18
+ if (Intl.ListFormat && Intl.ListFormat.supportedLocalesOf(['is']).length < 1) {
19
+ Intl.ListFormat = fixIcelandicLocale_privates_js_1._PatchedListFormat;
20
+ }
21
+ if (Intl.PluralRules && Intl.PluralRules.supportedLocalesOf(['is']).length < 1) {
22
+ Intl.PluralRules = fixIcelandicLocale_privates_js_1._PatchedPluralRules;
23
+ }
24
+ /* eslint-enable @typescript-eslint/no-unnecessary-type-assertion, @typescript-eslint/no-unnecessary-condition */
@@ -43,3 +43,19 @@ export declare const _patchedToLocaleDateString: {
43
43
  (locales?: Intl.LocalesArgument, options?: Intl.DateTimeFormatOptions | undefined): string;
44
44
  };
45
45
  };
46
+ export declare const _PatchedPluralRules: {
47
+ (locales?: string | string[] | undefined, options?: Intl.PluralRulesOptions | undefined): Intl.PluralRules;
48
+ new (locales?: string | string[] | undefined, options?: Intl.PluralRulesOptions | undefined): Intl.PluralRules;
49
+ supportedLocalesOf(locales: string | string[], options?: {
50
+ localeMatcher?: "best fit" | "lookup" | undefined;
51
+ } | undefined): string[];
52
+ } & {
53
+ $original: typeof Intl.PluralRules;
54
+ };
55
+ export declare const _PatchedListFormat: {
56
+ new (locales?: string | string[] | undefined, options?: Intl.ListFormatOptions | undefined): Intl.ListFormat;
57
+ prototype: Intl.ListFormat;
58
+ supportedLocalesOf(locales: string | string[], options?: Pick<Intl.ListFormatOptions, "localeMatcher"> | undefined): string[];
59
+ } & {
60
+ $original: typeof Intl.ListFormat;
61
+ };
@@ -1,9 +1,8 @@
1
1
  "use strict";
2
+ var _a, _b;
2
3
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports._patchedToLocaleDateString = exports._PatchedDateTimeFormat = exports._patchedToLocaleString = exports._PatchedNumberFormat = exports._patchedLocaleCompare = exports._PatchedCollator = void 0;
4
+ exports._PatchedListFormat = exports._PatchedPluralRules = exports._patchedToLocaleDateString = exports._PatchedDateTimeFormat = exports._patchedToLocaleString = exports._PatchedNumberFormat = exports._patchedLocaleCompare = exports._PatchedCollator = void 0;
4
5
  const _Collator = Intl.Collator;
5
- const _NumberFormat = Intl.NumberFormat;
6
- const _DateTimeFormat = Intl.DateTimeFormat;
7
6
  const mapLocales = (locales) => {
8
7
  locales = typeof locales === 'string' ? [locales] : locales || [];
9
8
  for (let i = 0, loc; (loc = locales[i]); i++) {
@@ -15,6 +14,8 @@ const mapLocales = (locales) => {
15
14
  // or "accent" then `ð` is consiered a variant of `d` and the letters
16
15
  // á, é, í, ó, ú, and ý are not treated as separate letters but simply
17
16
  // variants of a, e, i, o, u, and y.
17
+ // Also when the accented characters are part of a word, they are treated
18
+ // as fully equal to the base letter.
18
19
  return ['da'];
19
20
  }
20
21
  if (_Collator.supportedLocalesOf(loc).length) {
@@ -23,17 +24,35 @@ const mapLocales = (locales) => {
23
24
  }
24
25
  };
25
26
  const combineParts = (parts) => parts.map(({ value }) => value).join('');
26
- // ===========================================================================
27
- // Collator
28
- // ===========================================================================
29
27
  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;
28
+ if (!(this instanceof PatchedCollator)) {
29
+ // @ts-expect-error (YOLO! Can't be arsed)
30
+ return new PatchedCollator(locales, options);
31
+ }
32
+ const mappedLocales = mapLocales(locales);
33
+ this.super = _Collator(mappedLocales || locales, options);
34
+ this.mapped = !!mappedLocales;
35
+ };
36
+ // This is all very hacky, but extending the class *AND* preseving the
37
+ // ability to instantiate without `new` is a bit of a pain.
38
+ // Eagerly interested in finding a better way to do this.
39
+ const CollatorProto = {
40
+ constructor: PatchedCollator,
41
+ compare(a, b) {
42
+ const res1 = this.super.compare(a, b);
43
+ const a0 = a.charAt(0);
44
+ const b0 = b.charAt(0);
45
+ if (/\d/.test(a0 + b0)) {
46
+ return res1;
47
+ }
48
+ const res2 = this.super.compare(a0, b0);
49
+ return res2 !== 0 ? res2 : res1;
50
+ },
51
+ resolvedOptions() {
52
+ return this.super.resolvedOptions();
53
+ },
34
54
  };
35
- PatchedCollator.prototype = Object.create(_Collator.prototype);
36
- PatchedCollator.prototype.constructor = PatchedCollator;
55
+ PatchedCollator.prototype = CollatorProto;
37
56
  // Static methods (not patched since "is" is not ACTUALLY supported.)
38
57
  PatchedCollator.supportedLocalesOf = _Collator.supportedLocalesOf;
39
58
  PatchedCollator.$original = _Collator;
@@ -48,6 +67,7 @@ exports._patchedLocaleCompare.$original = _localeCompare;
48
67
  // ===========================================================================
49
68
  // NumberFormat
50
69
  // ===========================================================================
70
+ const _NumberFormat = Intl.NumberFormat;
51
71
  const reformatNumberParts = function (parts) {
52
72
  if (!this.mapped) {
53
73
  return parts;
@@ -197,6 +217,7 @@ const reformatDateTimeParts = function (parts) {
197
217
  });
198
218
  return parts;
199
219
  };
220
+ const _DateTimeFormat = Intl.DateTimeFormat;
200
221
  const PatchedDateTimeFormat = function DateTimeFormat(locales, options) {
201
222
  if (!(this instanceof PatchedDateTimeFormat)) {
202
223
  // @ts-expect-error (YOLO! Can't be arsed)
@@ -246,3 +267,73 @@ const _patchedToLocaleDateString = function toLocaleDateString(locales, options)
246
267
  };
247
268
  exports._patchedToLocaleDateString = _patchedToLocaleDateString;
248
269
  exports._patchedToLocaleDateString.$original = _toLocaleDateString;
270
+ // ===========================================================================
271
+ // PluralRules
272
+ // ===========================================================================
273
+ const _PluralRules = Intl.PluralRules;
274
+ let PatchedPluralRules;
275
+ // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition
276
+ if (_PluralRules) {
277
+ PatchedPluralRules = (_a = class PluralRules extends _PluralRules {
278
+ pluralIsl(n) {
279
+ return this.ord ? 'other' : n % 10 !== 1 || n % 100 === 11 ? 'other' : 'one';
280
+ }
281
+ constructor(locales, options) {
282
+ const mappedLocales = mapLocales(locales);
283
+ super(mappedLocales || locales, options);
284
+ this.mapped = !!mappedLocales;
285
+ this.ord = (options === null || options === void 0 ? void 0 : options.type) === 'ordinal';
286
+ }
287
+ select(n) {
288
+ if (this.mapped) {
289
+ // Pluralization function for Icelandic
290
+ // Copied over from https://www.npmjs.com/package/translate.js
291
+ return this.pluralIsl(n);
292
+ }
293
+ return super.select(n);
294
+ }
295
+ selectRange(n, n2) {
296
+ if (this.mapped) {
297
+ return this.pluralIsl(n2);
298
+ }
299
+ // @ts-expect-error (TS doesn't know about the .selectRange() method ...yet?)
300
+ return super.selectRange(n, n2);
301
+ }
302
+ },
303
+ _a.$original = _PluralRules,
304
+ _a);
305
+ }
306
+ exports._PatchedPluralRules = PatchedPluralRules;
307
+ // ===========================================================================
308
+ // ListFormat
309
+ // ===========================================================================
310
+ const _ListFormat = Intl.ListFormat;
311
+ let PatchedListFormat;
312
+ // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition
313
+ if (_ListFormat) {
314
+ PatchedListFormat = (_b = class ListFormat extends _ListFormat {
315
+ constructor(locales, options) {
316
+ const mappedLocales = mapLocales(locales);
317
+ super(mappedLocales || locales, options);
318
+ this.mapped = !!mappedLocales;
319
+ }
320
+ format(list) {
321
+ return this.mapped ? combineParts(this.formatToParts(list)) : super.format(list);
322
+ }
323
+ formatToParts(list) {
324
+ const parts = super.formatToParts(list);
325
+ if (this.mapped) {
326
+ for (const item of parts) {
327
+ const { value } = item;
328
+ if (item.type === 'literal' && (value === ' el. ' || value === ' eller ')) {
329
+ item.value = ' eða ';
330
+ }
331
+ }
332
+ }
333
+ return parts;
334
+ }
335
+ },
336
+ _b.$original = _ListFormat,
337
+ _b);
338
+ }
339
+ exports._PatchedListFormat = PatchedListFormat;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@reykjavik/webtools",
3
- "version": "0.1.17",
3
+ "version": "0.1.19",
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",