@visulima/humanizer 3.0.0-alpha.1 → 3.0.0-alpha.11

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/LICENSE.md CHANGED
@@ -1,6 +1,6 @@
1
1
  MIT License
2
2
 
3
- Copyright (c) 2025 visulima
3
+ Copyright (c) 2026 visulima
4
4
 
5
5
  Permission is hereby granted, free of charge, to any person obtaining a copy
6
6
  of this software and associated documentation files (the "Software"), to deal
package/README.md CHANGED
@@ -1,15 +1,24 @@
1
- <div align="center">
2
- <h3>Visulima humanizer</h3>
3
- <p>
4
- Humanizer is a library for humanizing data in a human-readable form.
5
- </p>
6
- </div>
1
+ <!-- START_PACKAGE_OG_IMAGE_PLACEHOLDER -->
2
+
3
+ <a href="https://www.anolilab.com/open-source" align="center">
4
+
5
+ <img src="__assets__/package-og.svg" alt="humanizer" />
6
+
7
+ </a>
8
+
9
+ <h3 align="center">Humanizer is a library for humanizing data in a human-readable form.</h3>
10
+
11
+ <!-- END_PACKAGE_OG_IMAGE_PLACEHOLDER -->
7
12
 
8
13
  <br />
9
14
 
10
15
  <div align="center">
11
16
 
12
- [![typescript-image]][typescript-url] [![npm-image]][npm-url] [![license-image]][license-url]
17
+ [![typescript-image][typescript-badge]][typescript-url]
18
+ [![mit licence][license-badge]][license]
19
+ [![npm downloads][npm-downloads-badge]][npm-downloads]
20
+ [![Chat][chat-badge]][chat]
21
+ [![PRs Welcome][prs-welcome-badge]][prs-welcome]
13
22
 
14
23
  </div>
15
24
 
@@ -1056,14 +1065,23 @@ If you would like to help take a look at the [list of issues](https://github.com
1056
1065
  - [Daniel Bannert](https://github.com/prisis)
1057
1066
  - [All Contributors](https://github.com/visulima/visulima/graphs/contributors)
1058
1067
 
1068
+ ## Made with ❤️ at Anolilab
1069
+
1070
+ This is an open source project and will always remain free to use. If you think it's cool, please star it 🌟. [Anolilab](https://www.anolilab.com/open-source) is a Development and AI Studio. Contact us at [hello@anolilab.com](mailto:hello@anolilab.com) if you need any help with these technologies or just want to say hi!
1071
+
1059
1072
  ## License
1060
1073
 
1061
- The visulima humanizer is open-sourced software licensed under the [MIT][license-url]
1074
+ The visulima humanizer is open-sourced software licensed under the [MIT][license]
1062
1075
 
1063
- [typescript-image]: https://img.shields.io/badge/Typescript-294E80.svg?style=for-the-badge&logo=typescript
1076
+ <!-- badges -->
1064
1077
 
1065
- [typescript-url]: https://www.typescriptlang.org/ "TypeScript" "typescript"
1066
- [license-image]: https://img.shields.io/npm/l/@visulima/humanizer?color=blueviolet&style=for-the-badge
1067
- [license-url]: LICENSE.md "license"
1068
- [npm-image]: https://img.shields.io/npm/v/@visulima/humanizer/latest.svg?style=for-the-badge&logo=npm
1069
- [npm-url]: https://www.npmjs.com/package/@visulima/humanizer/v/latest "npm"
1078
+ [license-badge]: https://img.shields.io/npm/l/@visulima/humanizer?style=for-the-badge
1079
+ [license]: https://github.com/visulima/visulima/blob/main/LICENSE
1080
+ [npm-downloads-badge]: https://img.shields.io/npm/dm/@visulima/humanizer?style=for-the-badge
1081
+ [npm-downloads]: https://www.npmjs.com/package/@visulima/humanizer
1082
+ [prs-welcome-badge]: https://img.shields.io/badge/PRs-welcome-brightgreen.svg?style=for-the-badge
1083
+ [prs-welcome]: https://github.com/visulima/visulima/blob/main/.github/CONTRIBUTING.md
1084
+ [chat-badge]: https://img.shields.io/discord/932323359193186354.svg?style=for-the-badge
1085
+ [chat]: https://discord.gg/TtFJY8xkFK
1086
+ [typescript-badge]: https://img.shields.io/badge/Typescript-294E80.svg?style=for-the-badge&logo=typescript
1087
+ [typescript-url]: https://www.typescriptlang.org/
package/dist/index.js CHANGED
@@ -1,3 +1,3 @@
1
- export { formatBytes, parseBytes } from './packem_shared/parseBytes-JWspeMzP.js';
2
- export { default as duration } from './packem_shared/duration-C62ipnQn.js';
3
- export { default as parseDuration } from './packem_shared/parseDuration-CeFaBgx9.js';
1
+ export { formatBytes, parseBytes } from './packem_shared/parseBytes-BkjYV2ut.js';
2
+ export { default as duration } from './packem_shared/duration-Br8Mr-Lj.js';
3
+ export { default as parseDuration } from './packem_shared/parseDuration-qO1Y4eiu.js';
@@ -28,15 +28,28 @@ const roUnitMap = {
28
28
  zi: "d",
29
29
  zile: "d"
30
30
  };
31
+ const romanianUnit = (unit) => (counter) => {
32
+ if (counter === 1) {
33
+ return unit[0];
34
+ }
35
+ if (Math.floor(counter) !== counter || counter === 0) {
36
+ return unit[1];
37
+ }
38
+ const remainder = counter % 100;
39
+ if (remainder >= 1 && remainder <= 19) {
40
+ return unit[1];
41
+ }
42
+ return unit[2];
43
+ };
31
44
  const durationLanguage = createDurationLanguage(
32
- (counter) => counter === 1 ? "an" : "ani",
33
- (counter) => counter === 1 ? "lună" : "luni",
34
- (counter) => counter === 1 ? "săptămână" : "săptămâni",
35
- (counter) => counter === 1 ? "zi" : "zile",
36
- (counter) => counter === 1 ? "oră" : "ore",
37
- (counter) => counter === 1 ? "minut" : "minute",
38
- (counter) => counter === 1 ? "secundă" : "secunde",
39
- (counter) => counter === 1 ? "milisecundă" : "milisecunde",
45
+ romanianUnit(["an", "ani", "de ani"]),
46
+ romanianUnit(["lună", "luni", "de luni"]),
47
+ romanianUnit(["săptămână", "săptămâni", "de săptămâni"]),
48
+ romanianUnit(["zi", "zile", "de zile"]),
49
+ romanianUnit(["oră", "ore", "de ore"]),
50
+ romanianUnit(["minut", "minute", "de minute"]),
51
+ romanianUnit(["secundă", "secunde", "de secunde"]),
52
+ romanianUnit(["milisecundă", "milisecunde", "de milisecunde"]),
40
53
  "peste %s",
41
54
  // "in %s"
42
55
  "%s în urmă",
@@ -1 +1,2 @@
1
+ import type { DurationLanguage } from "../types.d.ts";
1
2
  export declare const durationLanguage: DurationLanguage;
@@ -1,14 +1,15 @@
1
1
  const validateDurationLanguage = (language) => {
2
2
  const requiredProperties = ["y", "mo", "w", "d", "h", "m", "s", "ms", "future", "past"];
3
3
  for (const property of requiredProperties) {
4
- if (!Object.prototype.hasOwnProperty.call(language, property)) {
4
+ if (!Object.hasOwn(language, property)) {
5
5
  throw new TypeError(`Missing required property: ${property}`);
6
6
  }
7
7
  }
8
8
  if (typeof language.future !== "string" || typeof language.past !== "string") {
9
9
  throw new TypeError("Properties future and past must be of type string");
10
10
  }
11
- for (const property of ["y", "mo", "w", "d", "h", "m", "s", "ms"]) {
11
+ const unitProperties = ["y", "mo", "w", "d", "h", "m", "s", "ms"];
12
+ for (const property of unitProperties) {
12
13
  if (typeof language[property] !== "string" && typeof language[property] !== "function") {
13
14
  throw new TypeError(`Property ${property} must be of type string or function`);
14
15
  }
@@ -88,7 +88,7 @@ const getPieces = (ms, options) => {
88
88
  for (let index_ = index + 1; index_ < units.length; index_++) {
89
89
  const smallerUnitName = units[index_];
90
90
  const smallerUnitCount = unitCounts[smallerUnitName];
91
- unitCounts[unitName] += smallerUnitCount * unitMeasures[smallerUnitName] / unitMeasures[unitName];
91
+ unitCounts[unitName] = (unitCounts[unitName] ?? 0) + smallerUnitCount * unitMeasures[smallerUnitName] / unitMeasures[unitName];
92
92
  unitCounts[smallerUnitName] = 0;
93
93
  }
94
94
  break;
@@ -109,7 +109,7 @@ const getPieces = (ms, options) => {
109
109
  const previousUnitMs = unitMeasures[previousUnitName];
110
110
  const amountOfPreviousUnit = Math.floor(rounded * unitMeasures[unitName] / previousUnitMs);
111
111
  if (amountOfPreviousUnit) {
112
- unitCounts[previousUnitName] += amountOfPreviousUnit;
112
+ unitCounts[previousUnitName] = (unitCounts[previousUnitName] ?? 0) + amountOfPreviousUnit;
113
113
  unitCounts[unitName] = 0;
114
114
  } else {
115
115
  break;
@@ -158,18 +158,14 @@ const formatPieces = (pieces, options, ms) => {
158
158
  adverb = language.past ?? "";
159
159
  }
160
160
  }
161
- const renderedPieces = [];
162
- for (const piece_ of pieces) {
163
- const piece = piece_;
164
- renderedPieces.push(renderPiece(piece, language, options));
165
- }
161
+ const renderedPieces = pieces.map((piece) => renderPiece(piece, language, options));
166
162
  let result;
167
163
  if (!conjunction || pieces.length === 1) {
168
164
  result = renderedPieces.join(delimiter);
169
165
  } else if (pieces.length === 2) {
170
166
  result = renderedPieces.join(conjunction);
171
167
  } else {
172
- result = renderedPieces.slice(0, -1).join(delimiter) + (serialComma ? "," : "") + conjunction + renderedPieces.at(-1);
168
+ result = renderedPieces.slice(0, -1).join(delimiter) + (serialComma ? "," : "") + conjunction + (renderedPieces.at(-1) ?? "");
173
169
  }
174
170
  if (adverb) {
175
171
  result = adverb.replace("%s", result);
@@ -1,3 +1,13 @@
1
+ const PARSE_BYTES_REGEX = /^(?<value>-?\d+(?:[.,]\d+)*) *(?<type>[a-z]+)?$/i;
2
+ const KIBI_REGEX = /^KIBI/;
3
+ const MIBI_REGEX = /^MIBI/;
4
+ const GIBI_REGEX = /^GIBI/;
5
+ const TEBI_REGEX = /^TEBI/;
6
+ const PEBI_REGEX = /^PEBI/;
7
+ const EXBI_REGEX = /^EXBI/;
8
+ const ZEBI_REGEX = /^ZEBI/;
9
+ const YIBI_REGEX = /^YIBI/;
10
+ const IB_SUFFIX_REGEX = /^(.)IB$/;
1
11
  const BYTE_SIZES = {
2
12
  iec: [
3
13
  {
@@ -158,13 +168,17 @@ const parseLocalizedNumber = (stringNumber, locale) => {
158
168
  return Number.parseFloat(stringNumber.replaceAll(new RegExp(`\\${thousandSeparator}`, "g"), "").replace(new RegExp(`\\${decimalSeparator}`), "."));
159
169
  };
160
170
  const fromBase = (base) => {
161
- if (base === 2) {
162
- return 1024;
163
- }
164
- if (base === 10) {
165
- return 1e3;
171
+ switch (base) {
172
+ case 2: {
173
+ return 1024;
174
+ }
175
+ case 10: {
176
+ return 1e3;
177
+ }
178
+ default: {
179
+ throw new Error("Unsupported base.");
180
+ }
166
181
  }
167
- throw new TypeError(`Unsupported base.`);
168
182
  };
169
183
  const parseBytes = (value, options) => {
170
184
  const config = {
@@ -179,15 +193,13 @@ const parseBytes = (value, options) => {
179
193
  if (value.length > 100) {
180
194
  throw new TypeError("Value exceeds the maximum length of 100 characters.");
181
195
  }
182
- const match = /^(?<value>-?(?:\d+(([.,])\d+)*)?[.,]?\d+) *(?<type>bytes?|b|kb|kib|mb|mib|gb|gib|tb|tib|pb|pib|eb|eib|zb|zib|yb|yib|(kilo|kibi|mega|mebi|giga|gibi|tera|tebi|peta|pebi|exa|exbi|zetta|zebi|yotta|yobi)?bytes)?$/i.exec(
183
- value
184
- );
196
+ const match = PARSE_BYTES_REGEX.exec(value);
185
197
  const groups = match?.groups;
186
198
  if (!groups) {
187
199
  return Number.NaN;
188
200
  }
189
201
  const localizedNumber = parseLocalizedNumber(groups.value, config.locale);
190
- const type = (groups.type ?? "Bytes").toUpperCase().replace(/^KIBI/, "KILO").replace(/^MIBI/, "MEGA").replace(/^GIBI/, "GIGA").replace(/^TEBI/, "TERA").replace(/^PEBI/, "PETA").replace(/^EXBI/, "EXA").replace(/^ZEBI/, "ZETTA").replace(/^YIBI/, "YOTTA").replace(/^(.)IB$/, "$1B");
202
+ const type = (groups.type ?? "Bytes").toUpperCase().replace(KIBI_REGEX, "KILO").replace(MIBI_REGEX, "MEGA").replace(GIBI_REGEX, "GIGA").replace(TEBI_REGEX, "TERA").replace(PEBI_REGEX, "PETA").replace(EXBI_REGEX, "EXA").replace(ZEBI_REGEX, "ZETTA").replace(YIBI_REGEX, "YOTTA").replace(IB_SUFFIX_REGEX, "$1B");
191
203
  const level = BYTE_SIZES[config.units].findIndex((unit) => unit.short[0].toUpperCase() === type[0]);
192
204
  const base = fromBase(config.base);
193
205
  return localizedNumber * base ** level;
@@ -15,6 +15,7 @@ const ESCAPE_REGEX = /[-/\\^$*+?.()|[\]{}]/g;
15
15
  const ISO_FORMAT = /^PT(?:(\d+)H)?(?:(\d+)M)?(?:(\d+)S)?$/i;
16
16
  const COLON_FORMAT = /^(?:(\d+):)?(?:(\d+):)?(\d+)$/;
17
17
  const NUMERIC_STRING_REGEX = /^[+-]?\d+(?:\.\d+)?$/;
18
+ const SIGN_PREFIX_REGEX = /^[-+]/;
18
19
  const parseDuration = (value, options) => {
19
20
  if (typeof value !== "string" || value.length === 0) {
20
21
  return void 0;
@@ -53,14 +54,13 @@ const parseDuration = (value, options) => {
53
54
  if (colonMatch) {
54
55
  let hours = 0;
55
56
  let minutes = 0;
56
- let seconds = 0;
57
57
  if (colonMatch[2] !== void 0) {
58
58
  hours = Number.parseInt(colonMatch[1] ?? "0", 10);
59
59
  minutes = Number.parseInt(colonMatch[2], 10);
60
60
  } else if (colonMatch[1] !== void 0) {
61
61
  minutes = Number.parseInt(colonMatch[1], 10);
62
62
  }
63
- seconds = Number.parseInt(colonMatch[3] ?? "0", 10);
63
+ const seconds = Number.parseInt(colonMatch[3] ?? "0", 10);
64
64
  return hours * 36e5 + minutes * 6e4 + seconds * 1e3;
65
65
  }
66
66
  const currentUnitMapKeys = Object.keys(currentUnitMap);
@@ -84,7 +84,7 @@ const parseDuration = (value, options) => {
84
84
  }
85
85
  const trimmedNumberString = numberString.trim();
86
86
  const sign = trimmedNumberString.startsWith("-") ? -1 : 1;
87
- const absNumberString = trimmedNumberString.replace(/^[-+]/, "");
87
+ const absNumberString = trimmedNumberString.replace(SIGN_PREFIX_REGEX, "");
88
88
  const parsedNumber = Number.parseFloat(absNumberString);
89
89
  const unitKey = currentUnitMap[unitString.toLowerCase()];
90
90
  if (unitKey === void 0) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@visulima/humanizer",
3
- "version": "3.0.0-alpha.1",
3
+ "version": "3.0.0-alpha.11",
4
4
  "description": "Humanizer is a library for humanizing data in a human-readable form.",
5
5
  "keywords": [
6
6
  "visulima",
@@ -70,7 +70,7 @@
70
70
  "dist"
71
71
  ],
72
72
  "engines": {
73
- "node": ">=22.13 <=25.x"
73
+ "node": "^22.14.0 || >=24.10.0"
74
74
  },
75
75
  "publishConfig": {
76
76
  "access": "public",