@lacspace/humanize 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +51 -0
- package/README.md +69 -0
- package/dist/index.cjs +144 -0
- package/dist/index.cjs.map +1 -0
- package/dist/index.d.cts +51 -0
- package/dist/index.d.ts +51 -0
- package/dist/index.js +131 -0
- package/dist/index.js.map +1 -0
- package/package.json +21 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
Lacspace Free Licence
|
|
2
|
+
Version 1.0, August 2026
|
|
3
|
+
|
|
4
|
+
Copyright (c) 2026 Lacspace
|
|
5
|
+
|
|
6
|
+
PREAMBLE
|
|
7
|
+
|
|
8
|
+
This software is published by Lacspace under the Lacspace Free Licence โ a free,
|
|
9
|
+
permissive licence that lets you use this software for any purpose, including in
|
|
10
|
+
commercial products and services, at no cost. It grants the same freedoms as
|
|
11
|
+
common permissive open-source licences; the only condition is that this notice
|
|
12
|
+
travels with the software. The canonical, always-current text of this licence is
|
|
13
|
+
maintained at https://lacspace.com/licenses/lacspace-free-1.0
|
|
14
|
+
|
|
15
|
+
GRANT OF RIGHTS
|
|
16
|
+
|
|
17
|
+
Permission is hereby granted, free of charge, to any person or organisation
|
|
18
|
+
obtaining a copy of this software and its associated documentation and data files
|
|
19
|
+
(the "Software"), to deal in the Software without restriction, including without
|
|
20
|
+
limitation the rights to use, copy, modify, merge, publish, distribute,
|
|
21
|
+
sublicense, and/or sell copies of the Software, and to permit persons to whom the
|
|
22
|
+
Software is furnished to do so, subject to the conditions below. These rights are
|
|
23
|
+
granted for any purpose, personal or commercial, and are perpetual, worldwide,
|
|
24
|
+
non-exclusive, and royalty-free.
|
|
25
|
+
|
|
26
|
+
CONDITIONS
|
|
27
|
+
|
|
28
|
+
The above copyright notice, this permission notice, and the name of this licence
|
|
29
|
+
("Lacspace Free Licence") shall be included in all copies or substantial portions
|
|
30
|
+
of the Software.
|
|
31
|
+
|
|
32
|
+
TRADEMARKS
|
|
33
|
+
|
|
34
|
+
This licence does not grant permission to use the trade names, trademarks, service
|
|
35
|
+
marks, logos, or product names of Lacspace, except as required to reproduce the
|
|
36
|
+
notice above or to describe the origin of the Software in a truthful manner.
|
|
37
|
+
|
|
38
|
+
DISCLAIMER OF WARRANTY AND LIMITATION OF LIABILITY
|
|
39
|
+
|
|
40
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
41
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
|
|
42
|
+
FOR A PARTICULAR PURPOSE, AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
|
|
43
|
+
COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES, OR OTHER LIABILITY, WHETHER IN
|
|
44
|
+
AN ACTION OF CONTRACT, TORT, OR OTHERWISE, ARISING FROM, OUT OF, OR IN CONNECTION
|
|
45
|
+
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
|
46
|
+
|
|
47
|
+
---
|
|
48
|
+
|
|
49
|
+
The Lacspace Free Licence is a source-available, permissive licence and is not (as
|
|
50
|
+
of this version) an OSI-approved licence. In substance it grants the same freedoms
|
|
51
|
+
as the MIT Licence. Learn more at https://lacspace.com/licenses
|
package/README.md
ADDED
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
<div align="center">
|
|
2
|
+
|
|
3
|
+
# @lacspace/humanize
|
|
4
|
+
|
|
5
|
+
**Turn machine values into human-readable text โ bytes, durations, relative time, ordinals, plurals, compact numbers & lists.**
|
|
6
|
+
|
|
7
|
+
[](https://www.npmjs.com/package/@lacspace/humanize)
|
|
8
|
+
[](https://bundlephobia.com/package/@lacspace/humanize)
|
|
9
|
+
[](https://www.npmjs.com/package/@lacspace/humanize)
|
|
10
|
+
[](https://github.com/lacspace/npm-packages/blob/main/LICENSE)
|
|
11
|
+
|
|
12
|
+
</div>
|
|
13
|
+
|
|
14
|
+
> All the little "make it readable" helpers you reach for on every UI โ instead of installing `pretty-bytes` + `ms` + `humanize-duration` + a pluralize lib, get them as one tiny, typed, dependency-free package.
|
|
15
|
+
|
|
16
|
+
- ๐พ `bytes` / `parseBytes` โ `1536 โ "1.5 KB"` (and IEC KiB)
|
|
17
|
+
- โฑ๏ธ `duration` โ `90061000 โ "1d 1h"` (or long form)
|
|
18
|
+
- ๐ `relativeTime` โ `"3 hours ago"` / `"in 2 days"`
|
|
19
|
+
- ๐ข `ordinal`, `compact` (`1.2M`), `number` (grouping)
|
|
20
|
+
- โ๏ธ `pluralize`, `list` (`"a, b and c"`), `truncate`, `titleCase`
|
|
21
|
+
|
|
22
|
+
## Install
|
|
23
|
+
|
|
24
|
+
```bash
|
|
25
|
+
npm install @lacspace/humanize # or pnpm add / yarn add / bun add
|
|
26
|
+
```
|
|
27
|
+
|
|
28
|
+
## Use
|
|
29
|
+
|
|
30
|
+
```ts
|
|
31
|
+
import { bytes, duration, relativeTime, compact, ordinal, pluralize, list } from "@lacspace/humanize";
|
|
32
|
+
|
|
33
|
+
bytes(1536); // "1.5 KB"
|
|
34
|
+
duration(90061000); // "1d 1h"
|
|
35
|
+
relativeTime(Date.now() - 3.6e6); // "1 hour ago"
|
|
36
|
+
compact(1234567); // "1.2M"
|
|
37
|
+
ordinal(21); // "21st"
|
|
38
|
+
pluralize(3, "city"); // "3 cities"
|
|
39
|
+
list(["red", "green", "blue"]); // "red, green and blue"
|
|
40
|
+
```
|
|
41
|
+
|
|
42
|
+
## API
|
|
43
|
+
|
|
44
|
+
| Group | Functions |
|
|
45
|
+
| --- | --- |
|
|
46
|
+
| Size | `bytes`, `parseBytes` |
|
|
47
|
+
| Time | `duration`, `relativeTime` |
|
|
48
|
+
| Numbers | `ordinal`, `compact`, `number` |
|
|
49
|
+
| Words | `pluralize`, `plural`, `list`, `truncate`, `titleCase` |
|
|
50
|
+
|
|
51
|
+
Options let you switch to IEC units, long durations, more precision, Oxford commas and custom separators.
|
|
52
|
+
|
|
53
|
+
## Licensing
|
|
54
|
+
|
|
55
|
+
This package is **free** under the **[Lacspace Free Licence](https://lacspace.com/licenses/lacspace-free-1.0)** โ MIT-equivalent freedoms. Use it in personal and commercial projects at no cost; just keep the notice.
|
|
56
|
+
|
|
57
|
+
Not every Lacspace package is free. We also offer **Commercial**, **Client-specific** and **Private** packages under separate terms โ see the **[Lacspace Licence Centre](https://lacspace.com/licenses)**.
|
|
58
|
+
|
|
59
|
+
---
|
|
60
|
+
|
|
61
|
+
<div align="center">
|
|
62
|
+
|
|
63
|
+
**Part of the Lacspace ecosystem โ zero-dependency, isomorphic TypeScript packages.**
|
|
64
|
+
|
|
65
|
+
[All packages โ](https://lacspace.com/packages) ยท [npm org โ](https://www.npmjs.com/org/lacspace) ยท [Licence Centre โ](https://lacspace.com/licenses) ยท [GitHub โ](https://github.com/lacspace/npm-packages)
|
|
66
|
+
|
|
67
|
+
</div>
|
|
68
|
+
|
|
69
|
+
<div align="center"><sub>Built with care by <a href="https://lacspace.com">Lacspace</a> ยท Lacspace Free Licence ยท <a href="https://github.com/lacspace/npm-packages">source</a></sub></div>
|
package/dist/index.cjs
ADDED
|
@@ -0,0 +1,144 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
// src/index.ts
|
|
4
|
+
var BYTE_UNITS = ["B", "KB", "MB", "GB", "TB", "PB", "EB"];
|
|
5
|
+
var BYTE_UNITS_IEC = ["B", "KiB", "MiB", "GiB", "TiB", "PiB", "EiB"];
|
|
6
|
+
function bytes(n, opts = {}) {
|
|
7
|
+
const base = opts.binary ? 1024 : 1e3;
|
|
8
|
+
const units = opts.binary ? BYTE_UNITS_IEC : BYTE_UNITS;
|
|
9
|
+
const neg = n < 0 ? "-" : "";
|
|
10
|
+
let v = Math.abs(n);
|
|
11
|
+
if (v < base) return `${neg}${v} B`;
|
|
12
|
+
let i = 0;
|
|
13
|
+
while (v >= base && i < units.length - 1) {
|
|
14
|
+
v /= base;
|
|
15
|
+
i++;
|
|
16
|
+
}
|
|
17
|
+
const dp = opts.decimals ?? 1;
|
|
18
|
+
return `${neg}${v.toFixed(dp)} ${units[i]}`;
|
|
19
|
+
}
|
|
20
|
+
function parseBytes(input) {
|
|
21
|
+
const m = /^\s*(-?[\d.]+)\s*([a-z]+)?\s*$/i.exec(input);
|
|
22
|
+
if (!m) return NaN;
|
|
23
|
+
const value = parseFloat(m[1]);
|
|
24
|
+
const unit = (m[2] ?? "B").toUpperCase();
|
|
25
|
+
const binary = unit.includes("I");
|
|
26
|
+
const idx = (binary ? BYTE_UNITS_IEC : BYTE_UNITS).findIndex((u) => u.toUpperCase() === unit);
|
|
27
|
+
if (idx === -1) return unit === "B" ? value : NaN;
|
|
28
|
+
return value * (binary ? 1024 : 1e3) ** idx;
|
|
29
|
+
}
|
|
30
|
+
var DUR = [
|
|
31
|
+
{ u: "y", ms: 31536e6, long: "year" },
|
|
32
|
+
{ u: "mo", ms: 2592e6, long: "month" },
|
|
33
|
+
{ u: "d", ms: 864e5, long: "day" },
|
|
34
|
+
{ u: "h", ms: 36e5, long: "hour" },
|
|
35
|
+
{ u: "m", ms: 6e4, long: "minute" },
|
|
36
|
+
{ u: "s", ms: 1e3, long: "second" },
|
|
37
|
+
{ u: "ms", ms: 1, long: "millisecond" }
|
|
38
|
+
];
|
|
39
|
+
function duration(ms, opts = {}) {
|
|
40
|
+
const max = opts.units ?? 2;
|
|
41
|
+
let rem = Math.abs(Math.round(ms));
|
|
42
|
+
if (rem === 0) return opts.long ? "0 seconds" : "0s";
|
|
43
|
+
const parts = [];
|
|
44
|
+
for (const d of DUR) {
|
|
45
|
+
if (rem < d.ms) continue;
|
|
46
|
+
const count = Math.floor(rem / d.ms);
|
|
47
|
+
rem -= count * d.ms;
|
|
48
|
+
parts.push(opts.long ? `${count} ${plural(d.long, count)}` : `${count}${d.u}`);
|
|
49
|
+
if (parts.length >= max) break;
|
|
50
|
+
}
|
|
51
|
+
return (ms < 0 ? "-" : "") + parts.join(opts.long ? " " : " ");
|
|
52
|
+
}
|
|
53
|
+
var RT = [
|
|
54
|
+
[31536e3, "year"],
|
|
55
|
+
[2592e3, "month"],
|
|
56
|
+
[604800, "week"],
|
|
57
|
+
[86400, "day"],
|
|
58
|
+
[3600, "hour"],
|
|
59
|
+
[60, "minute"],
|
|
60
|
+
[1, "second"]
|
|
61
|
+
];
|
|
62
|
+
function relativeTime(date, base = Date.now()) {
|
|
63
|
+
const t = date instanceof Date ? date.getTime() : typeof date === "string" ? new Date(date).getTime() : date;
|
|
64
|
+
const b = base instanceof Date ? base.getTime() : base;
|
|
65
|
+
const diff = Math.round((b - t) / 1e3);
|
|
66
|
+
const abs = Math.abs(diff);
|
|
67
|
+
if (abs < 5) return "just now";
|
|
68
|
+
for (const [secs, unit] of RT) {
|
|
69
|
+
if (abs >= secs) {
|
|
70
|
+
const count = Math.floor(abs / secs);
|
|
71
|
+
const label = `${count} ${plural(unit, count)}`;
|
|
72
|
+
return diff >= 0 ? `${label} ago` : `in ${label}`;
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
return "just now";
|
|
76
|
+
}
|
|
77
|
+
function ordinal(n) {
|
|
78
|
+
const abs = Math.abs(n) % 100;
|
|
79
|
+
const last = abs % 10;
|
|
80
|
+
const suffix = abs >= 11 && abs <= 13 ? "th" : last === 1 ? "st" : last === 2 ? "nd" : last === 3 ? "rd" : "th";
|
|
81
|
+
return `${n}${suffix}`;
|
|
82
|
+
}
|
|
83
|
+
function compact(n, decimals = 1) {
|
|
84
|
+
const neg = n < 0 ? "-" : "";
|
|
85
|
+
const v = Math.abs(n);
|
|
86
|
+
const units = [[1e12, "T"], [1e9, "B"], [1e6, "M"], [1e3, "K"]];
|
|
87
|
+
for (const [threshold, u] of units) {
|
|
88
|
+
if (v >= threshold) {
|
|
89
|
+
const num = v / threshold;
|
|
90
|
+
return `${neg}${num % 1 === 0 ? num.toFixed(0) : num.toFixed(decimals)}${u}`;
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
return `${neg}${v}`;
|
|
94
|
+
}
|
|
95
|
+
function number(n, opts = {}) {
|
|
96
|
+
const sep = opts.separator ?? ",";
|
|
97
|
+
const dec = opts.decimal ?? ".";
|
|
98
|
+
const [int, frac] = String(n).split(".");
|
|
99
|
+
const sign = int.startsWith("-") ? "-" : "";
|
|
100
|
+
const digits = int.replace("-", "").replace(/\B(?=(\d{3})+(?!\d))/g, sep);
|
|
101
|
+
return sign + digits + (frac ? dec + frac : "");
|
|
102
|
+
}
|
|
103
|
+
function plural(word, count, pluralForm) {
|
|
104
|
+
if (count === 1) return word;
|
|
105
|
+
if (pluralForm) return pluralForm;
|
|
106
|
+
if (/(s|x|z|ch|sh)$/i.test(word)) return `${word}es`;
|
|
107
|
+
if (/[^aeiou]y$/i.test(word)) return `${word.slice(0, -1)}ies`;
|
|
108
|
+
return `${word}s`;
|
|
109
|
+
}
|
|
110
|
+
function pluralize(count, word, pluralForm) {
|
|
111
|
+
return `${count} ${plural(word, count, pluralForm)}`;
|
|
112
|
+
}
|
|
113
|
+
function list(items, opts = {}) {
|
|
114
|
+
const conj = opts.conjunction ?? "and";
|
|
115
|
+
if (items.length === 0) return "";
|
|
116
|
+
if (items.length === 1) return items[0];
|
|
117
|
+
if (items.length === 2) return `${items[0]} ${conj} ${items[1]}`;
|
|
118
|
+
const head = items.slice(0, -1).join(", ");
|
|
119
|
+
return `${head}${opts.oxford ? "," : ""} ${conj} ${items[items.length - 1]}`;
|
|
120
|
+
}
|
|
121
|
+
function truncate(text, maxLength, ellipsis = "\u2026") {
|
|
122
|
+
if (text.length <= maxLength) return text;
|
|
123
|
+
const cut = text.slice(0, maxLength - ellipsis.length);
|
|
124
|
+
const lastSpace = cut.lastIndexOf(" ");
|
|
125
|
+
return (lastSpace > maxLength * 0.6 ? cut.slice(0, lastSpace) : cut).replace(/[\s.,;:!?-]+$/, "") + ellipsis;
|
|
126
|
+
}
|
|
127
|
+
function titleCase(text) {
|
|
128
|
+
return text.toLowerCase().replace(/\b\w/g, (c) => c.toUpperCase());
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
exports.bytes = bytes;
|
|
132
|
+
exports.compact = compact;
|
|
133
|
+
exports.duration = duration;
|
|
134
|
+
exports.list = list;
|
|
135
|
+
exports.number = number;
|
|
136
|
+
exports.ordinal = ordinal;
|
|
137
|
+
exports.parseBytes = parseBytes;
|
|
138
|
+
exports.plural = plural;
|
|
139
|
+
exports.pluralize = pluralize;
|
|
140
|
+
exports.relativeTime = relativeTime;
|
|
141
|
+
exports.titleCase = titleCase;
|
|
142
|
+
exports.truncate = truncate;
|
|
143
|
+
//# sourceMappingURL=index.cjs.map
|
|
144
|
+
//# sourceMappingURL=index.cjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/index.ts"],"names":[],"mappings":"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","file":"index.cjs","sourcesContent":["/**\n * @lacspace/humanize\n *\n * Turn machine values into human-readable text โ bytes, durations, relative\n * time, ordinals, plurals, compact numbers and grammatical lists.\n * Zero dependencies, isomorphic.\n */\n\n/* ------------------------------ bytes ------------------------------ */\n\nconst BYTE_UNITS = [\"B\", \"KB\", \"MB\", \"GB\", \"TB\", \"PB\", \"EB\"];\nconst BYTE_UNITS_IEC = [\"B\", \"KiB\", \"MiB\", \"GiB\", \"TiB\", \"PiB\", \"EiB\"];\n\nexport interface BytesOptions {\n /** Use 1024-based IEC units (KiB, MiBโฆ). Default false (1000-based). */\n binary?: boolean;\n /** Decimal places. Default 1 (0 for bytes). */\n decimals?: number;\n}\n\n/** Human-readable file size. `bytes(1536)` โ \"1.5 KB\". */\nexport function bytes(n: number, opts: BytesOptions = {}): string {\n const base = opts.binary ? 1024 : 1000;\n const units = opts.binary ? BYTE_UNITS_IEC : BYTE_UNITS;\n const neg = n < 0 ? \"-\" : \"\";\n let v = Math.abs(n);\n if (v < base) return `${neg}${v} B`;\n let i = 0;\n while (v >= base && i < units.length - 1) { v /= base; i++; }\n const dp = opts.decimals ?? 1;\n return `${neg}${v.toFixed(dp)} ${units[i]}`;\n}\n\n/** Parse a size string back to bytes. `parseBytes(\"1.5 KB\")` โ 1500. */\nexport function parseBytes(input: string): number {\n const m = /^\\s*(-?[\\d.]+)\\s*([a-z]+)?\\s*$/i.exec(input);\n if (!m) return NaN;\n const value = parseFloat(m[1]!);\n const unit = (m[2] ?? \"B\").toUpperCase();\n const binary = unit.includes(\"I\");\n const idx = (binary ? BYTE_UNITS_IEC : BYTE_UNITS).findIndex((u) => u.toUpperCase() === unit);\n if (idx === -1) return unit === \"B\" ? value : NaN;\n return value * (binary ? 1024 : 1000) ** idx;\n}\n\n/* ------------------------------ duration ------------------------------ */\n\nconst DUR = [\n { u: \"y\", ms: 31536000000, long: \"year\" },\n { u: \"mo\", ms: 2592000000, long: \"month\" },\n { u: \"d\", ms: 86400000, long: \"day\" },\n { u: \"h\", ms: 3600000, long: \"hour\" },\n { u: \"m\", ms: 60000, long: \"minute\" },\n { u: \"s\", ms: 1000, long: \"second\" },\n { u: \"ms\", ms: 1, long: \"millisecond\" },\n];\n\nexport interface DurationOptions {\n /** Max number of units to show. Default 2. */\n units?: number;\n /** Use full words (\"2 hours 3 minutes\"). Default false (\"2h 3m\"). */\n long?: boolean;\n}\n\n/** Human-readable duration. `duration(90061000)` โ \"1d 1h\". */\nexport function duration(ms: number, opts: DurationOptions = {}): string {\n const max = opts.units ?? 2;\n let rem = Math.abs(Math.round(ms));\n if (rem === 0) return opts.long ? \"0 seconds\" : \"0s\";\n const parts: string[] = [];\n for (const d of DUR) {\n if (rem < d.ms) continue;\n const count = Math.floor(rem / d.ms);\n rem -= count * d.ms;\n parts.push(opts.long ? `${count} ${plural(d.long, count)}` : `${count}${d.u}`);\n if (parts.length >= max) break;\n }\n return (ms < 0 ? \"-\" : \"\") + parts.join(opts.long ? \" \" : \" \");\n}\n\n/* ------------------------------ relative time ------------------------------ */\n\nconst RT: [number, string][] = [\n [31536000, \"year\"], [2592000, \"month\"], [604800, \"week\"],\n [86400, \"day\"], [3600, \"hour\"], [60, \"minute\"], [1, \"second\"],\n];\n\n/** Relative time. `relativeTime(Date.now() - 3600e3)` โ \"1 hour ago\". */\nexport function relativeTime(date: Date | number | string, base: Date | number = Date.now()): string {\n const t = date instanceof Date ? date.getTime() : typeof date === \"string\" ? new Date(date).getTime() : date;\n const b = base instanceof Date ? base.getTime() : base;\n const diff = Math.round((b - t) / 1000);\n const abs = Math.abs(diff);\n if (abs < 5) return \"just now\";\n for (const [secs, unit] of RT) {\n if (abs >= secs) {\n const count = Math.floor(abs / secs);\n const label = `${count} ${plural(unit, count)}`;\n return diff >= 0 ? `${label} ago` : `in ${label}`;\n }\n }\n return \"just now\";\n}\n\n/* ------------------------------ numbers ------------------------------ */\n\n/** Ordinal suffix. `ordinal(21)` โ \"21st\". */\nexport function ordinal(n: number): string {\n const abs = Math.abs(n) % 100;\n const last = abs % 10;\n const suffix = abs >= 11 && abs <= 13 ? \"th\" : last === 1 ? \"st\" : last === 2 ? \"nd\" : last === 3 ? \"rd\" : \"th\";\n return `${n}${suffix}`;\n}\n\n/** Compact number. `compact(1234567)` โ \"1.2M\". */\nexport function compact(n: number, decimals = 1): string {\n const neg = n < 0 ? \"-\" : \"\";\n const v = Math.abs(n);\n const units: [number, string][] = [[1e12, \"T\"], [1e9, \"B\"], [1e6, \"M\"], [1e3, \"K\"]];\n for (const [threshold, u] of units) {\n if (v >= threshold) {\n const num = v / threshold;\n return `${neg}${(num % 1 === 0 ? num.toFixed(0) : num.toFixed(decimals))}${u}`;\n }\n }\n return `${neg}${v}`;\n}\n\n/** Group digits with separators. `number(1234567.5)` โ \"1,234,567.5\". */\nexport function number(n: number, opts: { separator?: string; decimal?: string } = {}): string {\n const sep = opts.separator ?? \",\";\n const dec = opts.decimal ?? \".\";\n const [int, frac] = String(n).split(\".\");\n const sign = int!.startsWith(\"-\") ? \"-\" : \"\";\n const digits = int!.replace(\"-\", \"\").replace(/\\B(?=(\\d{3})+(?!\\d))/g, sep);\n return sign + digits + (frac ? dec + frac : \"\");\n}\n\n/* ------------------------------ words ------------------------------ */\n\n/** Pluralize a word for a count (naive English + irregulars). */\nexport function plural(word: string, count: number, pluralForm?: string): string {\n if (count === 1) return word;\n if (pluralForm) return pluralForm;\n if (/(s|x|z|ch|sh)$/i.test(word)) return `${word}es`;\n if (/[^aeiou]y$/i.test(word)) return `${word.slice(0, -1)}ies`;\n return `${word}s`;\n}\n\n/** \"5 items\" / \"1 item\". */\nexport function pluralize(count: number, word: string, pluralForm?: string): string {\n return `${count} ${plural(word, count, pluralForm)}`;\n}\n\n/** Grammatical list. `list([\"a\",\"b\",\"c\"])` โ \"a, b and c\". */\nexport function list(items: string[], opts: { conjunction?: string; oxford?: boolean } = {}): string {\n const conj = opts.conjunction ?? \"and\";\n if (items.length === 0) return \"\";\n if (items.length === 1) return items[0]!;\n if (items.length === 2) return `${items[0]} ${conj} ${items[1]}`;\n const head = items.slice(0, -1).join(\", \");\n return `${head}${opts.oxford ? \",\" : \"\"} ${conj} ${items[items.length - 1]}`;\n}\n\n/** Truncate to a max length with an ellipsis on a word boundary. */\nexport function truncate(text: string, maxLength: number, ellipsis = \"โฆ\"): string {\n if (text.length <= maxLength) return text;\n const cut = text.slice(0, maxLength - ellipsis.length);\n const lastSpace = cut.lastIndexOf(\" \");\n return (lastSpace > maxLength * 0.6 ? cut.slice(0, lastSpace) : cut).replace(/[\\s.,;:!?-]+$/, \"\") + ellipsis;\n}\n\n/** Title Case a string. */\nexport function titleCase(text: string): string {\n return text.toLowerCase().replace(/\\b\\w/g, (c) => c.toUpperCase());\n}\n"]}
|
package/dist/index.d.cts
ADDED
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @lacspace/humanize
|
|
3
|
+
*
|
|
4
|
+
* Turn machine values into human-readable text โ bytes, durations, relative
|
|
5
|
+
* time, ordinals, plurals, compact numbers and grammatical lists.
|
|
6
|
+
* Zero dependencies, isomorphic.
|
|
7
|
+
*/
|
|
8
|
+
interface BytesOptions {
|
|
9
|
+
/** Use 1024-based IEC units (KiB, MiBโฆ). Default false (1000-based). */
|
|
10
|
+
binary?: boolean;
|
|
11
|
+
/** Decimal places. Default 1 (0 for bytes). */
|
|
12
|
+
decimals?: number;
|
|
13
|
+
}
|
|
14
|
+
/** Human-readable file size. `bytes(1536)` โ "1.5 KB". */
|
|
15
|
+
declare function bytes(n: number, opts?: BytesOptions): string;
|
|
16
|
+
/** Parse a size string back to bytes. `parseBytes("1.5 KB")` โ 1500. */
|
|
17
|
+
declare function parseBytes(input: string): number;
|
|
18
|
+
interface DurationOptions {
|
|
19
|
+
/** Max number of units to show. Default 2. */
|
|
20
|
+
units?: number;
|
|
21
|
+
/** Use full words ("2 hours 3 minutes"). Default false ("2h 3m"). */
|
|
22
|
+
long?: boolean;
|
|
23
|
+
}
|
|
24
|
+
/** Human-readable duration. `duration(90061000)` โ "1d 1h". */
|
|
25
|
+
declare function duration(ms: number, opts?: DurationOptions): string;
|
|
26
|
+
/** Relative time. `relativeTime(Date.now() - 3600e3)` โ "1 hour ago". */
|
|
27
|
+
declare function relativeTime(date: Date | number | string, base?: Date | number): string;
|
|
28
|
+
/** Ordinal suffix. `ordinal(21)` โ "21st". */
|
|
29
|
+
declare function ordinal(n: number): string;
|
|
30
|
+
/** Compact number. `compact(1234567)` โ "1.2M". */
|
|
31
|
+
declare function compact(n: number, decimals?: number): string;
|
|
32
|
+
/** Group digits with separators. `number(1234567.5)` โ "1,234,567.5". */
|
|
33
|
+
declare function number(n: number, opts?: {
|
|
34
|
+
separator?: string;
|
|
35
|
+
decimal?: string;
|
|
36
|
+
}): string;
|
|
37
|
+
/** Pluralize a word for a count (naive English + irregulars). */
|
|
38
|
+
declare function plural(word: string, count: number, pluralForm?: string): string;
|
|
39
|
+
/** "5 items" / "1 item". */
|
|
40
|
+
declare function pluralize(count: number, word: string, pluralForm?: string): string;
|
|
41
|
+
/** Grammatical list. `list(["a","b","c"])` โ "a, b and c". */
|
|
42
|
+
declare function list(items: string[], opts?: {
|
|
43
|
+
conjunction?: string;
|
|
44
|
+
oxford?: boolean;
|
|
45
|
+
}): string;
|
|
46
|
+
/** Truncate to a max length with an ellipsis on a word boundary. */
|
|
47
|
+
declare function truncate(text: string, maxLength: number, ellipsis?: string): string;
|
|
48
|
+
/** Title Case a string. */
|
|
49
|
+
declare function titleCase(text: string): string;
|
|
50
|
+
|
|
51
|
+
export { type BytesOptions, type DurationOptions, bytes, compact, duration, list, number, ordinal, parseBytes, plural, pluralize, relativeTime, titleCase, truncate };
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @lacspace/humanize
|
|
3
|
+
*
|
|
4
|
+
* Turn machine values into human-readable text โ bytes, durations, relative
|
|
5
|
+
* time, ordinals, plurals, compact numbers and grammatical lists.
|
|
6
|
+
* Zero dependencies, isomorphic.
|
|
7
|
+
*/
|
|
8
|
+
interface BytesOptions {
|
|
9
|
+
/** Use 1024-based IEC units (KiB, MiBโฆ). Default false (1000-based). */
|
|
10
|
+
binary?: boolean;
|
|
11
|
+
/** Decimal places. Default 1 (0 for bytes). */
|
|
12
|
+
decimals?: number;
|
|
13
|
+
}
|
|
14
|
+
/** Human-readable file size. `bytes(1536)` โ "1.5 KB". */
|
|
15
|
+
declare function bytes(n: number, opts?: BytesOptions): string;
|
|
16
|
+
/** Parse a size string back to bytes. `parseBytes("1.5 KB")` โ 1500. */
|
|
17
|
+
declare function parseBytes(input: string): number;
|
|
18
|
+
interface DurationOptions {
|
|
19
|
+
/** Max number of units to show. Default 2. */
|
|
20
|
+
units?: number;
|
|
21
|
+
/** Use full words ("2 hours 3 minutes"). Default false ("2h 3m"). */
|
|
22
|
+
long?: boolean;
|
|
23
|
+
}
|
|
24
|
+
/** Human-readable duration. `duration(90061000)` โ "1d 1h". */
|
|
25
|
+
declare function duration(ms: number, opts?: DurationOptions): string;
|
|
26
|
+
/** Relative time. `relativeTime(Date.now() - 3600e3)` โ "1 hour ago". */
|
|
27
|
+
declare function relativeTime(date: Date | number | string, base?: Date | number): string;
|
|
28
|
+
/** Ordinal suffix. `ordinal(21)` โ "21st". */
|
|
29
|
+
declare function ordinal(n: number): string;
|
|
30
|
+
/** Compact number. `compact(1234567)` โ "1.2M". */
|
|
31
|
+
declare function compact(n: number, decimals?: number): string;
|
|
32
|
+
/** Group digits with separators. `number(1234567.5)` โ "1,234,567.5". */
|
|
33
|
+
declare function number(n: number, opts?: {
|
|
34
|
+
separator?: string;
|
|
35
|
+
decimal?: string;
|
|
36
|
+
}): string;
|
|
37
|
+
/** Pluralize a word for a count (naive English + irregulars). */
|
|
38
|
+
declare function plural(word: string, count: number, pluralForm?: string): string;
|
|
39
|
+
/** "5 items" / "1 item". */
|
|
40
|
+
declare function pluralize(count: number, word: string, pluralForm?: string): string;
|
|
41
|
+
/** Grammatical list. `list(["a","b","c"])` โ "a, b and c". */
|
|
42
|
+
declare function list(items: string[], opts?: {
|
|
43
|
+
conjunction?: string;
|
|
44
|
+
oxford?: boolean;
|
|
45
|
+
}): string;
|
|
46
|
+
/** Truncate to a max length with an ellipsis on a word boundary. */
|
|
47
|
+
declare function truncate(text: string, maxLength: number, ellipsis?: string): string;
|
|
48
|
+
/** Title Case a string. */
|
|
49
|
+
declare function titleCase(text: string): string;
|
|
50
|
+
|
|
51
|
+
export { type BytesOptions, type DurationOptions, bytes, compact, duration, list, number, ordinal, parseBytes, plural, pluralize, relativeTime, titleCase, truncate };
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,131 @@
|
|
|
1
|
+
// src/index.ts
|
|
2
|
+
var BYTE_UNITS = ["B", "KB", "MB", "GB", "TB", "PB", "EB"];
|
|
3
|
+
var BYTE_UNITS_IEC = ["B", "KiB", "MiB", "GiB", "TiB", "PiB", "EiB"];
|
|
4
|
+
function bytes(n, opts = {}) {
|
|
5
|
+
const base = opts.binary ? 1024 : 1e3;
|
|
6
|
+
const units = opts.binary ? BYTE_UNITS_IEC : BYTE_UNITS;
|
|
7
|
+
const neg = n < 0 ? "-" : "";
|
|
8
|
+
let v = Math.abs(n);
|
|
9
|
+
if (v < base) return `${neg}${v} B`;
|
|
10
|
+
let i = 0;
|
|
11
|
+
while (v >= base && i < units.length - 1) {
|
|
12
|
+
v /= base;
|
|
13
|
+
i++;
|
|
14
|
+
}
|
|
15
|
+
const dp = opts.decimals ?? 1;
|
|
16
|
+
return `${neg}${v.toFixed(dp)} ${units[i]}`;
|
|
17
|
+
}
|
|
18
|
+
function parseBytes(input) {
|
|
19
|
+
const m = /^\s*(-?[\d.]+)\s*([a-z]+)?\s*$/i.exec(input);
|
|
20
|
+
if (!m) return NaN;
|
|
21
|
+
const value = parseFloat(m[1]);
|
|
22
|
+
const unit = (m[2] ?? "B").toUpperCase();
|
|
23
|
+
const binary = unit.includes("I");
|
|
24
|
+
const idx = (binary ? BYTE_UNITS_IEC : BYTE_UNITS).findIndex((u) => u.toUpperCase() === unit);
|
|
25
|
+
if (idx === -1) return unit === "B" ? value : NaN;
|
|
26
|
+
return value * (binary ? 1024 : 1e3) ** idx;
|
|
27
|
+
}
|
|
28
|
+
var DUR = [
|
|
29
|
+
{ u: "y", ms: 31536e6, long: "year" },
|
|
30
|
+
{ u: "mo", ms: 2592e6, long: "month" },
|
|
31
|
+
{ u: "d", ms: 864e5, long: "day" },
|
|
32
|
+
{ u: "h", ms: 36e5, long: "hour" },
|
|
33
|
+
{ u: "m", ms: 6e4, long: "minute" },
|
|
34
|
+
{ u: "s", ms: 1e3, long: "second" },
|
|
35
|
+
{ u: "ms", ms: 1, long: "millisecond" }
|
|
36
|
+
];
|
|
37
|
+
function duration(ms, opts = {}) {
|
|
38
|
+
const max = opts.units ?? 2;
|
|
39
|
+
let rem = Math.abs(Math.round(ms));
|
|
40
|
+
if (rem === 0) return opts.long ? "0 seconds" : "0s";
|
|
41
|
+
const parts = [];
|
|
42
|
+
for (const d of DUR) {
|
|
43
|
+
if (rem < d.ms) continue;
|
|
44
|
+
const count = Math.floor(rem / d.ms);
|
|
45
|
+
rem -= count * d.ms;
|
|
46
|
+
parts.push(opts.long ? `${count} ${plural(d.long, count)}` : `${count}${d.u}`);
|
|
47
|
+
if (parts.length >= max) break;
|
|
48
|
+
}
|
|
49
|
+
return (ms < 0 ? "-" : "") + parts.join(opts.long ? " " : " ");
|
|
50
|
+
}
|
|
51
|
+
var RT = [
|
|
52
|
+
[31536e3, "year"],
|
|
53
|
+
[2592e3, "month"],
|
|
54
|
+
[604800, "week"],
|
|
55
|
+
[86400, "day"],
|
|
56
|
+
[3600, "hour"],
|
|
57
|
+
[60, "minute"],
|
|
58
|
+
[1, "second"]
|
|
59
|
+
];
|
|
60
|
+
function relativeTime(date, base = Date.now()) {
|
|
61
|
+
const t = date instanceof Date ? date.getTime() : typeof date === "string" ? new Date(date).getTime() : date;
|
|
62
|
+
const b = base instanceof Date ? base.getTime() : base;
|
|
63
|
+
const diff = Math.round((b - t) / 1e3);
|
|
64
|
+
const abs = Math.abs(diff);
|
|
65
|
+
if (abs < 5) return "just now";
|
|
66
|
+
for (const [secs, unit] of RT) {
|
|
67
|
+
if (abs >= secs) {
|
|
68
|
+
const count = Math.floor(abs / secs);
|
|
69
|
+
const label = `${count} ${plural(unit, count)}`;
|
|
70
|
+
return diff >= 0 ? `${label} ago` : `in ${label}`;
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
return "just now";
|
|
74
|
+
}
|
|
75
|
+
function ordinal(n) {
|
|
76
|
+
const abs = Math.abs(n) % 100;
|
|
77
|
+
const last = abs % 10;
|
|
78
|
+
const suffix = abs >= 11 && abs <= 13 ? "th" : last === 1 ? "st" : last === 2 ? "nd" : last === 3 ? "rd" : "th";
|
|
79
|
+
return `${n}${suffix}`;
|
|
80
|
+
}
|
|
81
|
+
function compact(n, decimals = 1) {
|
|
82
|
+
const neg = n < 0 ? "-" : "";
|
|
83
|
+
const v = Math.abs(n);
|
|
84
|
+
const units = [[1e12, "T"], [1e9, "B"], [1e6, "M"], [1e3, "K"]];
|
|
85
|
+
for (const [threshold, u] of units) {
|
|
86
|
+
if (v >= threshold) {
|
|
87
|
+
const num = v / threshold;
|
|
88
|
+
return `${neg}${num % 1 === 0 ? num.toFixed(0) : num.toFixed(decimals)}${u}`;
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
return `${neg}${v}`;
|
|
92
|
+
}
|
|
93
|
+
function number(n, opts = {}) {
|
|
94
|
+
const sep = opts.separator ?? ",";
|
|
95
|
+
const dec = opts.decimal ?? ".";
|
|
96
|
+
const [int, frac] = String(n).split(".");
|
|
97
|
+
const sign = int.startsWith("-") ? "-" : "";
|
|
98
|
+
const digits = int.replace("-", "").replace(/\B(?=(\d{3})+(?!\d))/g, sep);
|
|
99
|
+
return sign + digits + (frac ? dec + frac : "");
|
|
100
|
+
}
|
|
101
|
+
function plural(word, count, pluralForm) {
|
|
102
|
+
if (count === 1) return word;
|
|
103
|
+
if (pluralForm) return pluralForm;
|
|
104
|
+
if (/(s|x|z|ch|sh)$/i.test(word)) return `${word}es`;
|
|
105
|
+
if (/[^aeiou]y$/i.test(word)) return `${word.slice(0, -1)}ies`;
|
|
106
|
+
return `${word}s`;
|
|
107
|
+
}
|
|
108
|
+
function pluralize(count, word, pluralForm) {
|
|
109
|
+
return `${count} ${plural(word, count, pluralForm)}`;
|
|
110
|
+
}
|
|
111
|
+
function list(items, opts = {}) {
|
|
112
|
+
const conj = opts.conjunction ?? "and";
|
|
113
|
+
if (items.length === 0) return "";
|
|
114
|
+
if (items.length === 1) return items[0];
|
|
115
|
+
if (items.length === 2) return `${items[0]} ${conj} ${items[1]}`;
|
|
116
|
+
const head = items.slice(0, -1).join(", ");
|
|
117
|
+
return `${head}${opts.oxford ? "," : ""} ${conj} ${items[items.length - 1]}`;
|
|
118
|
+
}
|
|
119
|
+
function truncate(text, maxLength, ellipsis = "\u2026") {
|
|
120
|
+
if (text.length <= maxLength) return text;
|
|
121
|
+
const cut = text.slice(0, maxLength - ellipsis.length);
|
|
122
|
+
const lastSpace = cut.lastIndexOf(" ");
|
|
123
|
+
return (lastSpace > maxLength * 0.6 ? cut.slice(0, lastSpace) : cut).replace(/[\s.,;:!?-]+$/, "") + ellipsis;
|
|
124
|
+
}
|
|
125
|
+
function titleCase(text) {
|
|
126
|
+
return text.toLowerCase().replace(/\b\w/g, (c) => c.toUpperCase());
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
export { bytes, compact, duration, list, number, ordinal, parseBytes, plural, pluralize, relativeTime, titleCase, truncate };
|
|
130
|
+
//# sourceMappingURL=index.js.map
|
|
131
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/index.ts"],"names":[],"mappings":"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","file":"index.js","sourcesContent":["/**\n * @lacspace/humanize\n *\n * Turn machine values into human-readable text โ bytes, durations, relative\n * time, ordinals, plurals, compact numbers and grammatical lists.\n * Zero dependencies, isomorphic.\n */\n\n/* ------------------------------ bytes ------------------------------ */\n\nconst BYTE_UNITS = [\"B\", \"KB\", \"MB\", \"GB\", \"TB\", \"PB\", \"EB\"];\nconst BYTE_UNITS_IEC = [\"B\", \"KiB\", \"MiB\", \"GiB\", \"TiB\", \"PiB\", \"EiB\"];\n\nexport interface BytesOptions {\n /** Use 1024-based IEC units (KiB, MiBโฆ). Default false (1000-based). */\n binary?: boolean;\n /** Decimal places. Default 1 (0 for bytes). */\n decimals?: number;\n}\n\n/** Human-readable file size. `bytes(1536)` โ \"1.5 KB\". */\nexport function bytes(n: number, opts: BytesOptions = {}): string {\n const base = opts.binary ? 1024 : 1000;\n const units = opts.binary ? BYTE_UNITS_IEC : BYTE_UNITS;\n const neg = n < 0 ? \"-\" : \"\";\n let v = Math.abs(n);\n if (v < base) return `${neg}${v} B`;\n let i = 0;\n while (v >= base && i < units.length - 1) { v /= base; i++; }\n const dp = opts.decimals ?? 1;\n return `${neg}${v.toFixed(dp)} ${units[i]}`;\n}\n\n/** Parse a size string back to bytes. `parseBytes(\"1.5 KB\")` โ 1500. */\nexport function parseBytes(input: string): number {\n const m = /^\\s*(-?[\\d.]+)\\s*([a-z]+)?\\s*$/i.exec(input);\n if (!m) return NaN;\n const value = parseFloat(m[1]!);\n const unit = (m[2] ?? \"B\").toUpperCase();\n const binary = unit.includes(\"I\");\n const idx = (binary ? BYTE_UNITS_IEC : BYTE_UNITS).findIndex((u) => u.toUpperCase() === unit);\n if (idx === -1) return unit === \"B\" ? value : NaN;\n return value * (binary ? 1024 : 1000) ** idx;\n}\n\n/* ------------------------------ duration ------------------------------ */\n\nconst DUR = [\n { u: \"y\", ms: 31536000000, long: \"year\" },\n { u: \"mo\", ms: 2592000000, long: \"month\" },\n { u: \"d\", ms: 86400000, long: \"day\" },\n { u: \"h\", ms: 3600000, long: \"hour\" },\n { u: \"m\", ms: 60000, long: \"minute\" },\n { u: \"s\", ms: 1000, long: \"second\" },\n { u: \"ms\", ms: 1, long: \"millisecond\" },\n];\n\nexport interface DurationOptions {\n /** Max number of units to show. Default 2. */\n units?: number;\n /** Use full words (\"2 hours 3 minutes\"). Default false (\"2h 3m\"). */\n long?: boolean;\n}\n\n/** Human-readable duration. `duration(90061000)` โ \"1d 1h\". */\nexport function duration(ms: number, opts: DurationOptions = {}): string {\n const max = opts.units ?? 2;\n let rem = Math.abs(Math.round(ms));\n if (rem === 0) return opts.long ? \"0 seconds\" : \"0s\";\n const parts: string[] = [];\n for (const d of DUR) {\n if (rem < d.ms) continue;\n const count = Math.floor(rem / d.ms);\n rem -= count * d.ms;\n parts.push(opts.long ? `${count} ${plural(d.long, count)}` : `${count}${d.u}`);\n if (parts.length >= max) break;\n }\n return (ms < 0 ? \"-\" : \"\") + parts.join(opts.long ? \" \" : \" \");\n}\n\n/* ------------------------------ relative time ------------------------------ */\n\nconst RT: [number, string][] = [\n [31536000, \"year\"], [2592000, \"month\"], [604800, \"week\"],\n [86400, \"day\"], [3600, \"hour\"], [60, \"minute\"], [1, \"second\"],\n];\n\n/** Relative time. `relativeTime(Date.now() - 3600e3)` โ \"1 hour ago\". */\nexport function relativeTime(date: Date | number | string, base: Date | number = Date.now()): string {\n const t = date instanceof Date ? date.getTime() : typeof date === \"string\" ? new Date(date).getTime() : date;\n const b = base instanceof Date ? base.getTime() : base;\n const diff = Math.round((b - t) / 1000);\n const abs = Math.abs(diff);\n if (abs < 5) return \"just now\";\n for (const [secs, unit] of RT) {\n if (abs >= secs) {\n const count = Math.floor(abs / secs);\n const label = `${count} ${plural(unit, count)}`;\n return diff >= 0 ? `${label} ago` : `in ${label}`;\n }\n }\n return \"just now\";\n}\n\n/* ------------------------------ numbers ------------------------------ */\n\n/** Ordinal suffix. `ordinal(21)` โ \"21st\". */\nexport function ordinal(n: number): string {\n const abs = Math.abs(n) % 100;\n const last = abs % 10;\n const suffix = abs >= 11 && abs <= 13 ? \"th\" : last === 1 ? \"st\" : last === 2 ? \"nd\" : last === 3 ? \"rd\" : \"th\";\n return `${n}${suffix}`;\n}\n\n/** Compact number. `compact(1234567)` โ \"1.2M\". */\nexport function compact(n: number, decimals = 1): string {\n const neg = n < 0 ? \"-\" : \"\";\n const v = Math.abs(n);\n const units: [number, string][] = [[1e12, \"T\"], [1e9, \"B\"], [1e6, \"M\"], [1e3, \"K\"]];\n for (const [threshold, u] of units) {\n if (v >= threshold) {\n const num = v / threshold;\n return `${neg}${(num % 1 === 0 ? num.toFixed(0) : num.toFixed(decimals))}${u}`;\n }\n }\n return `${neg}${v}`;\n}\n\n/** Group digits with separators. `number(1234567.5)` โ \"1,234,567.5\". */\nexport function number(n: number, opts: { separator?: string; decimal?: string } = {}): string {\n const sep = opts.separator ?? \",\";\n const dec = opts.decimal ?? \".\";\n const [int, frac] = String(n).split(\".\");\n const sign = int!.startsWith(\"-\") ? \"-\" : \"\";\n const digits = int!.replace(\"-\", \"\").replace(/\\B(?=(\\d{3})+(?!\\d))/g, sep);\n return sign + digits + (frac ? dec + frac : \"\");\n}\n\n/* ------------------------------ words ------------------------------ */\n\n/** Pluralize a word for a count (naive English + irregulars). */\nexport function plural(word: string, count: number, pluralForm?: string): string {\n if (count === 1) return word;\n if (pluralForm) return pluralForm;\n if (/(s|x|z|ch|sh)$/i.test(word)) return `${word}es`;\n if (/[^aeiou]y$/i.test(word)) return `${word.slice(0, -1)}ies`;\n return `${word}s`;\n}\n\n/** \"5 items\" / \"1 item\". */\nexport function pluralize(count: number, word: string, pluralForm?: string): string {\n return `${count} ${plural(word, count, pluralForm)}`;\n}\n\n/** Grammatical list. `list([\"a\",\"b\",\"c\"])` โ \"a, b and c\". */\nexport function list(items: string[], opts: { conjunction?: string; oxford?: boolean } = {}): string {\n const conj = opts.conjunction ?? \"and\";\n if (items.length === 0) return \"\";\n if (items.length === 1) return items[0]!;\n if (items.length === 2) return `${items[0]} ${conj} ${items[1]}`;\n const head = items.slice(0, -1).join(\", \");\n return `${head}${opts.oxford ? \",\" : \"\"} ${conj} ${items[items.length - 1]}`;\n}\n\n/** Truncate to a max length with an ellipsis on a word boundary. */\nexport function truncate(text: string, maxLength: number, ellipsis = \"โฆ\"): string {\n if (text.length <= maxLength) return text;\n const cut = text.slice(0, maxLength - ellipsis.length);\n const lastSpace = cut.lastIndexOf(\" \");\n return (lastSpace > maxLength * 0.6 ? cut.slice(0, lastSpace) : cut).replace(/[\\s.,;:!?-]+$/, \"\") + ellipsis;\n}\n\n/** Title Case a string. */\nexport function titleCase(text: string): string {\n return text.toLowerCase().replace(/\\b\\w/g, (c) => c.toUpperCase());\n}\n"]}
|
package/package.json
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@lacspace/humanize",
|
|
3
|
+
"version": "1.0.0",
|
|
4
|
+
"description": "Turn machine values into human-readable text โ bytes, durations, relative time, ordinals, plurals, compact numbers and grammatical lists. Zero-dependency, isomorphic.",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"main": "./dist/index.cjs",
|
|
7
|
+
"module": "./dist/index.js",
|
|
8
|
+
"types": "./dist/index.d.ts",
|
|
9
|
+
"exports": { ".": { "import": { "types": "./dist/index.d.ts", "default": "./dist/index.js" }, "require": { "types": "./dist/index.d.cts", "default": "./dist/index.cjs" } } },
|
|
10
|
+
"files": ["dist"],
|
|
11
|
+
"sideEffects": false,
|
|
12
|
+
"scripts": { "build": "tsup", "prepublishOnly": "npm run build" },
|
|
13
|
+
"keywords": ["humanize", "pretty-bytes", "format-bytes", "duration", "relative-time", "time-ago", "ordinal", "pluralize", "compact-number", "format-number", "zero-dependency", "isomorphic", "typescript"],
|
|
14
|
+
"author": "Lacspace <contact@lacspace.com>",
|
|
15
|
+
"license": "SEE LICENSE IN LICENSE",
|
|
16
|
+
"homepage": "https://lacspace.com/packages",
|
|
17
|
+
"repository": { "type": "git", "url": "git+https://github.com/lacspace/npm-packages.git", "directory": "humanize" },
|
|
18
|
+
"bugs": { "url": "https://github.com/lacspace/npm-packages/issues" },
|
|
19
|
+
"engines": { "node": ">=18" },
|
|
20
|
+
"publishConfig": { "access": "public" }
|
|
21
|
+
}
|