cizgile 0.0.1 → 0.1.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 +27 -0
- package/README.md +291 -0
- package/dist/index.d.mts +55 -0
- package/dist/index.mjs +430 -0
- package/dist/shared/iri-CzbwHYgr.mjs +572 -0
- package/dist/shared/locales-latin-D8enWKzw.mjs +372 -0
- package/dist/shared/types-C1iMvUXh.d.mts +13 -0
- package/dist/transliterate.d.mts +85 -0
- package/dist/transliterate.mjs +643 -0
- package/dist/uri.d.mts +156 -0
- package/dist/uri.mjs +344 -0
- package/package.json +78 -6
- package/index.js +0 -1
package/LICENSE
ADDED
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 productdevbook
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
|
22
|
+
|
|
23
|
+
---
|
|
24
|
+
|
|
25
|
+
Transliteration table values are derived from simov/slugify (MIT, Copyright (c)
|
|
26
|
+
Simeon Velichkov) and sindresorhus/transliterate (MIT, Copyright (c) Sindre
|
|
27
|
+
Sorhus).
|
package/README.md
ADDED
|
@@ -0,0 +1,291 @@
|
|
|
1
|
+
<p align="center">
|
|
2
|
+
<br>
|
|
3
|
+
<img src=".github/assets/cover.svg?v=1a3fd12" alt="cizgile — Zero-dependency URL slug engine" width="100%">
|
|
4
|
+
<br><br>
|
|
5
|
+
<b style="font-size: 2em;">cizgile</b>
|
|
6
|
+
<br><br>
|
|
7
|
+
Zero-dependency URL slug engine.
|
|
8
|
+
<br>
|
|
9
|
+
Turn any title into a clean URL slug — in any language — and work with URLs the way RFC 3986 and RFC 3987 describe them. Pure TypeScript, works everywhere.
|
|
10
|
+
<br><br>
|
|
11
|
+
<a href="https://npmjs.com/package/cizgile"><img src="https://img.shields.io/npm/v/cizgile?style=flat&colorA=18181B&colorB=34d399" alt="npm version"></a>
|
|
12
|
+
<a href="https://npmjs.com/package/cizgile"><img src="https://img.shields.io/npm/dm/cizgile?style=flat&colorA=18181B&colorB=34d399" alt="npm downloads"></a>
|
|
13
|
+
<a href="https://bundlephobia.com/result?p=cizgile"><img src="https://img.shields.io/bundlephobia/minzip/cizgile?style=flat&colorA=18181B&colorB=34d399" alt="bundle size"></a>
|
|
14
|
+
<a href="https://github.com/productdevbook/cizgile/blob/main/LICENSE"><img src="https://img.shields.io/github/license/productdevbook/cizgile?style=flat&colorA=18181B&colorB=34d399" alt="license"></a>
|
|
15
|
+
</p>
|
|
16
|
+
|
|
17
|
+
## Install
|
|
18
|
+
|
|
19
|
+
```sh
|
|
20
|
+
npm install cizgile
|
|
21
|
+
```
|
|
22
|
+
|
|
23
|
+
```ts
|
|
24
|
+
import { slugify } from "cizgile"
|
|
25
|
+
|
|
26
|
+
slugify("Hello, World!") // "hello-world"
|
|
27
|
+
slugify("İstanbul Şişli & Çığ", { locale: "tr" }) // "istanbul-sisli-ve-cig"
|
|
28
|
+
slugify("Straße Über Ärger", { locale: "de" }) // "strasse-ueber-aerger"
|
|
29
|
+
slugify("你好 World", { unicode: true }) // "你好-world"
|
|
30
|
+
```
|
|
31
|
+
|
|
32
|
+
No dependencies. ESM only. Node 20+, Bun, Deno, browsers, edge workers.
|
|
33
|
+
|
|
34
|
+
## Why cizgile
|
|
35
|
+
|
|
36
|
+
- **Slugs that are correct by construction.** Every ASCII slug is a valid URL path segment (RFC 3986 `segment-nz-nc`) — no percent-encoding needed, no `.` or `..`, no accidental scheme prefix.
|
|
37
|
+
- **Speaks your language.** 19 locales (`tr`, `de`, `da`, `sv`, `uk`, `bg`, …) and 7 scripts (Latin, Cyrillic, Greek, Arabic, Armenian, Georgian, Dhivehi). `ß` → `ss`, `İ` → `i`, `Щ` → `shch`.
|
|
38
|
+
- **Unicode slugs when you want them.** `你好-world` stays readable, and `iriToUri` gives you the exact percent-encoded form for the wire.
|
|
39
|
+
- **A real URL toolkit underneath.** Resolve, normalise, compare, validate and relativise URLs by the RFC, cross-checked against the WHATWG `URL` parser.
|
|
40
|
+
- **Small and tree-shakeable.** `import { slugify }` ships the Latin table only; other scripts load only when you import them.
|
|
41
|
+
|
|
42
|
+
## Three entry points
|
|
43
|
+
|
|
44
|
+
| import | what you get |
|
|
45
|
+
| ----------------------- | --------------------------------------------------------------------------------------------- |
|
|
46
|
+
| `cizgile` | `slugify`, `isSlug`, `createSlugger`, `truncateSlug`, `decamelize`, script and bidi guards |
|
|
47
|
+
| `cizgile/transliterate` | `transliterate`, per-script tables, locales, `defineLocale` |
|
|
48
|
+
| `cizgile/uri` | percent-encoding, `resolveUri`, `normalizeUri`, `relativize`, validators, IRI ↔ URI, punycode |
|
|
49
|
+
|
|
50
|
+
## Slugs
|
|
51
|
+
|
|
52
|
+
### Everyday use
|
|
53
|
+
|
|
54
|
+
```ts
|
|
55
|
+
import { slugify } from "cizgile"
|
|
56
|
+
|
|
57
|
+
slugify("Déjà Vu!") // "deja-vu"
|
|
58
|
+
slugify("don't stop") // "dont-stop"
|
|
59
|
+
slugify("v1.2.3", { preserveCharacters: ["."] }) // "v1.2.3"
|
|
60
|
+
slugify("Hello World", { separator: "_" }) // "hello_world"
|
|
61
|
+
slugify("Donald E. Knuth", { lowercase: false }) // "Donald-E-Knuth"
|
|
62
|
+
slugify("getHTTPResponse", { decamelize: true }) // "get-http-response"
|
|
63
|
+
slugify("the quick brown fox", { maxLength: 9 }) // "the-quick"
|
|
64
|
+
slugify("C++ & Rust", { replacements: [["C++", "cpp"]] }) // "cpp-and-rust"
|
|
65
|
+
```
|
|
66
|
+
|
|
67
|
+
### Languages and scripts
|
|
68
|
+
|
|
69
|
+
Locale ids for Latin-script languages; Cyrillic locales and other scripts come from `cizgile/transliterate` so they only end up in your bundle when you use them.
|
|
70
|
+
|
|
71
|
+
```ts
|
|
72
|
+
import { slugify } from "cizgile"
|
|
73
|
+
import { cyrillic, greek, uk, defineLocale, de } from "cizgile/transliterate"
|
|
74
|
+
|
|
75
|
+
slugify("Çay & Simit", { locale: "tr" }) // "cay-ve-simit"
|
|
76
|
+
slugify("Fisch & Chips", { locale: "de" }) // "fisch-und-chips"
|
|
77
|
+
slugify("Ærø", { locale: "da" }) // "aeroe"
|
|
78
|
+
slugify("Київ", { locale: uk }) // "kyiv"
|
|
79
|
+
slugify("Привет мир", { transliterate: [cyrillic] }) // "privet-mir"
|
|
80
|
+
slugify("Καλημέρα", { transliterate: [greek] }) // "kalimera"
|
|
81
|
+
|
|
82
|
+
const swiss = defineLocale(de, { id: "de-CH", table: { ß: "ss" } })
|
|
83
|
+
slugify("Straße", { locale: swiss }) // "strasse"
|
|
84
|
+
```
|
|
85
|
+
|
|
86
|
+
Locale ids: `az da de es fi fr hu it nb nl pt sv tr vi`. Locale objects: those plus `bg mk ru sr uk`.
|
|
87
|
+
|
|
88
|
+
### Unicode slugs
|
|
89
|
+
|
|
90
|
+
```ts
|
|
91
|
+
import { slugify } from "cizgile"
|
|
92
|
+
import { iriToUri, uriToIri } from "cizgile/uri"
|
|
93
|
+
|
|
94
|
+
const slug = slugify("Ünïcödé final ①", { unicode: true }) // "ünïcödé-final-1"
|
|
95
|
+
const wire = iriToUri(slug) // "%C3%BCn%C3%AFc%C3%B6d%C3%A9-final-1"
|
|
96
|
+
uriToIri(wire) === slug // true
|
|
97
|
+
```
|
|
98
|
+
|
|
99
|
+
Unicode slugs keep letters, digits and combining marks, are NFKC-normalised, never start with a mark and contain no invisible or bidi-control characters. Two optional guards for user-supplied titles:
|
|
100
|
+
|
|
101
|
+
```ts
|
|
102
|
+
slugify("pаypal", { unicode: true, scripts: "single" }) // throws — that "а" is Cyrillic
|
|
103
|
+
slugify("مرحبا 123", { unicode: true, bidi: "encode" }) // "%D9%85%D8%B1%D8%AD%D8%A8%D8%A7-123"
|
|
104
|
+
```
|
|
105
|
+
|
|
106
|
+
`scripts` applies the UTS #39 restriction levels (`"single"`, `"highly-restrictive"`, `"moderately-restrictive"`, `"any"`); `bidi` enforces RFC 3987 §4.2 (`"allow"`, `"encode"`, `"throw"`).
|
|
107
|
+
|
|
108
|
+
### Unique slugs
|
|
109
|
+
|
|
110
|
+
```ts
|
|
111
|
+
import { createSlugger } from "cizgile"
|
|
112
|
+
|
|
113
|
+
const slug = createSlugger()
|
|
114
|
+
slug("Hello") // "hello"
|
|
115
|
+
slug("Hello") // "hello-2"
|
|
116
|
+
slug("hello-2") // "hello-2-2" — never a duplicate
|
|
117
|
+
slug.reset()
|
|
118
|
+
```
|
|
119
|
+
|
|
120
|
+
### Validation
|
|
121
|
+
|
|
122
|
+
```ts
|
|
123
|
+
import { isSlug } from "cizgile"
|
|
124
|
+
|
|
125
|
+
isSlug("hello-world") // true
|
|
126
|
+
isSlug("Hello World") // false
|
|
127
|
+
isSlug("hello_world", { separator: "_" }) // true
|
|
128
|
+
isSlug("你好-world", { unicode: true }) // true
|
|
129
|
+
```
|
|
130
|
+
|
|
131
|
+
`isSlug` accepts exactly what `slugify` would produce under the same options.
|
|
132
|
+
|
|
133
|
+
### All options
|
|
134
|
+
|
|
135
|
+
| option | default | what it does |
|
|
136
|
+
| --------------------------- | --------- | ----------------------------------------------------------------------------------------------------------------------------- |
|
|
137
|
+
| `separator` | `"-"` | Joins words. Any URL-safe punctuation (`- _ . ~ !$&'()*+,;= @`) or `""`. |
|
|
138
|
+
| `lowercase` | `true` | `false` keeps the original case. |
|
|
139
|
+
| `unicode` | `false` | Keep letters from every script instead of transliterating to ASCII. |
|
|
140
|
+
| `locale` | — | Language-specific rules: a locale id or a `Locale` object. |
|
|
141
|
+
| `transliterate` | `true` | `false` skips the tables (accents still fold); an array adds script tables. |
|
|
142
|
+
| `decamelize` | `false` | `fooBar` → `foo-bar`, `HTMLParser` → `html-parser`. |
|
|
143
|
+
| `replacements` | `[]` | `[from, to]` pairs applied first; spaces in `to` become separators. |
|
|
144
|
+
| `remove` | `/['’]/g` | Characters to delete rather than turn into separators (`don't` → `dont`). |
|
|
145
|
+
| `preserveCharacters` | `[]` | Extra URL-safe characters to keep, e.g. `["."]` for version numbers. |
|
|
146
|
+
| `preserveLeadingUnderscore` | `false` | `_draft` → `_draft`. |
|
|
147
|
+
| `preserveTrailingSeparator` | `false` | Keep a trailing separator while the user is still typing. |
|
|
148
|
+
| `maxLength` | — | Cut at a word boundary, never inside a character (emoji sequences, combining marks). Counts UTF-16 code units like `.length`. |
|
|
149
|
+
| `scripts` | `"any"` | Unicode mode: UTS #39 mixed-script restriction level. |
|
|
150
|
+
| `bidi` | `"allow"` | Unicode mode: RFC 3987 §4.2 direction rule — `"encode"` or `"throw"` on violation. |
|
|
151
|
+
|
|
152
|
+
The pipeline runs in this order: strip control/format characters → NFC → `replacements` → NFKC → `decamelize` → transliterate (locale → your tables → Latin → symbols → strip accents) → lowercase → `remove` → separators → `maxLength` → guards. Output is idempotent: `slugify(slugify(x)) === slugify(x)`.
|
|
153
|
+
|
|
154
|
+
## Transliteration on its own
|
|
155
|
+
|
|
156
|
+
```ts
|
|
157
|
+
import { transliterate, cyrillic, locales } from "cizgile/transliterate"
|
|
158
|
+
|
|
159
|
+
transliterate("Straße Ærø") // "Strasse AEro"
|
|
160
|
+
transliterate("Привет", { tables: [cyrillic] }) // "Privet"
|
|
161
|
+
transliterate("Ängsö", { locale: locales.sv }) // "Aengsoe"
|
|
162
|
+
transliterate("你好") // "你好" — unknown scripts are kept (use unknown: "drop" to remove)
|
|
163
|
+
```
|
|
164
|
+
|
|
165
|
+
Tables: `latin symbols cyrillic cyrillicUk cyrillicBg cyrillicMk cyrillicSr greek arabic persian urdu pashto armenian georgian dhivehi`, plus `allScripts`. Where a letter is spelled differently at the start of a word (Armenian `ե`, Ukrainian `є ї й ю я`), the capital carries the word-initial form. `defineLocale` and `mergeTables` return new objects — nothing global is ever mutated.
|
|
166
|
+
|
|
167
|
+
## URL toolkit
|
|
168
|
+
|
|
169
|
+
Everything in `cizgile/uri` follows RFC 3986 / RFC 3987 to the letter and is tested against the RFC's own examples and the WHATWG `URL` parser.
|
|
170
|
+
|
|
171
|
+
```ts
|
|
172
|
+
import {
|
|
173
|
+
resolveUri,
|
|
174
|
+
relativize,
|
|
175
|
+
normalizeUri,
|
|
176
|
+
equivalentUris,
|
|
177
|
+
encodePathSegment,
|
|
178
|
+
percentEncode,
|
|
179
|
+
percentDecode,
|
|
180
|
+
isUri,
|
|
181
|
+
isAbsoluteUri,
|
|
182
|
+
isIPv6Address,
|
|
183
|
+
extractUri,
|
|
184
|
+
iriToUri,
|
|
185
|
+
uriToIri,
|
|
186
|
+
domainToAscii,
|
|
187
|
+
} from "cizgile/uri"
|
|
188
|
+
|
|
189
|
+
resolveUri("http://a/b/c/d;p?q", "../../g") // "http://a/g"
|
|
190
|
+
relativize("http://a/b/c/d;p?q", "http://a/b/g") // "../g"
|
|
191
|
+
normalizeUri("HTTP://www.EXAMPLE.com:80/%7e%41/./b/../c") // "http://www.example.com/~A/c"
|
|
192
|
+
equivalentUris("http://example.com", "http://example.com:80/") // true
|
|
193
|
+
encodePathSegment("a/b?c") // "a%2Fb%3Fc"
|
|
194
|
+
percentEncode("À ア") // "%C3%80%20%E3%82%A2"
|
|
195
|
+
isAbsoluteUri("http://a/b#c") // false — fragments are not allowed in an absolute-URI
|
|
196
|
+
isIPv6Address("::ffff:192.0.2.1") // true
|
|
197
|
+
extractUri("<http://a/b>.") // "http://a/b"
|
|
198
|
+
iriToUri("http://例え.jp/résumé", { host: "punycode" }) // "http://xn--r8jz45g.jp/r%C3%A9sum%C3%A9"
|
|
199
|
+
```
|
|
200
|
+
|
|
201
|
+
<details>
|
|
202
|
+
<summary><b>Full reference</b></summary>
|
|
203
|
+
|
|
204
|
+
**Characters and percent-encoding (RFC 3986 §2)**
|
|
205
|
+
`isUnreserved` `isReserved` `isGenDelim` `isSubDelim` `isPchar` `isSegmentNzNc` `isQueryChar` `isScheme` — per code point.
|
|
206
|
+
`percentEncode(text, keep?)` — UTF-8, uppercase hex. `keep` names a set: RFC `"unreserved" "pchar" "segment-nz-nc" "path" "query" "fragment" "userinfo"`, WHATWG `"whatwg-c0-control" "whatwg-fragment" "whatwg-query" "whatwg-special-query" "whatwg-path" "whatwg-userinfo" "whatwg-component" "form"`, or a predicate.
|
|
207
|
+
`percentDecode(text, { plusAsSpace })`, `normalizePercentEncoding(text)`.
|
|
208
|
+
`encodePathSegment(seg, { noColon })`, `encodePath(path, { relative })`, `encodeQuery`, `encodeFragment`, `encodeForm`.
|
|
209
|
+
|
|
210
|
+
**Hosts (§3.2.2)**
|
|
211
|
+
`isIPv4Address` `isIPv6Address` `isIPvFuture` `isIPLiteral` `isRegName` `isHost` `parseHost` `parseAuthority` `serializeAuthority`. `0x7f.0.0.1` and `2130706433` are registered names, not addresses (§7.4).
|
|
212
|
+
|
|
213
|
+
**Parsing and validation (§4, Appendix A/B)**
|
|
214
|
+
`parseUri` `serializeUri` — components stay distinct from "absent"; the serializer inserts `/.` or `./` where the grammar requires it.
|
|
215
|
+
`isUriReference` `isUri` `isAbsoluteUri` `isRelativeReference` `classifyReference` `pathForm` — validating parser built from the ABNF.
|
|
216
|
+
`isIriReference` `isIri` `isIunreserved` `isIpchar` — the same for IRIs (RFC 3987 §2.2).
|
|
217
|
+
`extractUri(text)` — Appendix C: strips `<>`, quotes, `URL:` prefixes, trailing punctuation and line-wrap whitespace.
|
|
218
|
+
|
|
219
|
+
**Resolution (§5)**
|
|
220
|
+
`resolveUri(base, ref, { strict, allowRelativeBase })` — every §5.4 example passes; strict by default (`http:g` stays `http:g`).
|
|
221
|
+
`relativize(base, target)` — shortest reference that resolves back to `target`.
|
|
222
|
+
`removeDotSegments(path)` — the literal two-buffer algorithm. `mergePaths(base, refPath)`.
|
|
223
|
+
`isSameDocumentReference(base, ref, { normalize })`.
|
|
224
|
+
|
|
225
|
+
**Normalisation and comparison (§6)**
|
|
226
|
+
`normalizeUri(uri, { defaultPorts, schemeBased, userinfo })` — case, percent-encoding, dot segments, default ports, empty path → `/`; `userinfo: "strip-password" | "strip"` for logs.
|
|
227
|
+
`normalizePath(path, { trailingSlash })`.
|
|
228
|
+
`equivalentUris(a, b, { level, base, ignoreFragment })` — `"simple"`, `"syntax"` or `"scheme"` (default). Never maps IRIs to URIs (RFC 3987 §5.3.1).
|
|
229
|
+
|
|
230
|
+
**IRIs (RFC 3987)**
|
|
231
|
+
`isUcschar` `isIprivate` `isBidiControl` `hasBidiControls`.
|
|
232
|
+
`iriToUri(iri, { bidi, nfc, strict, host })` — percent-encodes without altering characters (§3.1 step 1c); `host: "punycode"` converts the domain; `strict` rejects characters no IRI may contain.
|
|
233
|
+
`uriToIri(uri)` — decodes only what §3.2 allows, per component.
|
|
234
|
+
`punycodeEncode` `punycodeDecode` `domainToAscii` `domainToUnicode` — RFC 3492, no dependencies.
|
|
235
|
+
|
|
236
|
+
Deliberately not implemented: RFC 6874 IPv6 zone identifiers (reverted by RFC 9844) and the network-based normalisation of §6.2.4.
|
|
237
|
+
|
|
238
|
+
</details>
|
|
239
|
+
|
|
240
|
+
## For AI agents
|
|
241
|
+
|
|
242
|
+
If you are an assistant writing code with this library, these are the facts that matter:
|
|
243
|
+
|
|
244
|
+
- Import paths: `cizgile` (slugs), `cizgile/transliterate` (tables, locales), `cizgile/uri` (URLs). ESM only, no default exports, no side effects, no runtime dependencies.
|
|
245
|
+
- `slugify(text, options?)` returns `""` for input with nothing usable — it never throws on ordinary text. It throws `RangeError`/`TypeError` only for invalid options (`separator: "/"`, `preserveCharacters` containing the separator, a non-global `remove` regex, a negative `maxLength`) and, in unicode mode, when `scripts` or `bidi: "throw"` rejects the result.
|
|
246
|
+
- The ASCII output is always a valid path segment; put it in a URL as-is. For `unicode: true` output, call `iriToUri(slug)` before putting it on the wire.
|
|
247
|
+
- Cyrillic and other non-Latin scripts are opt-in: pass `transliterate: [cyrillic]` or a locale object such as `uk` from `cizgile/transliterate`. Without them, Cyrillic text produces `""` in ASCII mode.
|
|
248
|
+
- `createSlugger()` is the way to get unique slugs in a document or import job; do not append counters yourself.
|
|
249
|
+
- Use `resolveUri`, `normalizeUri` and `equivalentUris` instead of string concatenation or `new URL()` when you need RFC behaviour (strict scheme handling, no special-scheme rewriting, no host IDNA unless you ask for it).
|
|
250
|
+
- Every exported function has an explicit TypeScript signature; the `.d.mts` files in `dist/` are the authoritative API.
|
|
251
|
+
|
|
252
|
+
## How it compares
|
|
253
|
+
|
|
254
|
+
| input | cizgile | Django `slugify` | Rails `parameterize` | `@sindresorhus/slugify` |
|
|
255
|
+
| --------------------------- | -------------------- | ---------------- | -------------------- | ----------------------- |
|
|
256
|
+
| `" Joel is a slug "` | `joel-is-a-slug` | same | same | same |
|
|
257
|
+
| `"jack & jill"` | `jack-and-jill` | `jack-jill` | `jack-jill` | `jack-and-jill` |
|
|
258
|
+
| `"don't"` | `dont` | `dont` | `don-t` | `dont` |
|
|
259
|
+
| `"fooBar"` | `foobar` | `foobar` | `foobar` | `foo-bar` |
|
|
260
|
+
| `"snake_case"` | `snake-case` | `snake_case` | `snake_case` | `snake-case` |
|
|
261
|
+
| `"Straße"` (`locale: "de"`) | `strasse` | `strae` | `strasse` | `strasse` |
|
|
262
|
+
| `"Привет"` | `""` (opt-in tables) | `""` | `""` | `privet` |
|
|
263
|
+
|
|
264
|
+
`decamelize` is off by default (Django/Rails behaviour) and `&` is spelled out (sindresorhus behaviour); both are one option away.
|
|
265
|
+
|
|
266
|
+
## Specifications
|
|
267
|
+
|
|
268
|
+
RFC 3986 (with errata 2033, 4547, 4789, 5428), RFC 3987, RFC 3492, RFC 8820, RFC 9844, the WHATWG URL Standard's percent-encode sets, Unicode UTS #39 restriction levels and UAX #29 grapheme boundaries, Google Search Central's URL guidance. The test suite runs every example those documents contain.
|
|
269
|
+
|
|
270
|
+
## Development
|
|
271
|
+
|
|
272
|
+
```sh
|
|
273
|
+
bun install
|
|
274
|
+
bun run test # oxlint, oxfmt, tsc, vitest under node, then vitest under bun
|
|
275
|
+
bun run build # rolldown → dist/*.mjs + dist/*.d.mts
|
|
276
|
+
bun run coverage
|
|
277
|
+
bun run release # bumpp: bump, tag, push — the tag publishes to npm
|
|
278
|
+
```
|
|
279
|
+
|
|
280
|
+
## Credits
|
|
281
|
+
|
|
282
|
+
- [simov/slugify](https://github.com/simov/slugify) — the charmap + per-locale override idea and most Cyrillic, Greek, Arabic and symbol values.
|
|
283
|
+
- [sindresorhus/slugify](https://github.com/sindresorhus/slugify) and [sindresorhus/transliterate](https://github.com/sindresorhus/transliterate) — `decamelize`, custom replacements, the counter slugger, and the Armenian, Georgian and Dhivehi tables.
|
|
284
|
+
- [Django](https://github.com/django/django) and [Rails](https://github.com/rails/rails) — the reference behaviours the parity tests are written against.
|
|
285
|
+
- The [WHATWG URL Standard](https://url.spec.whatwg.org/) — percent-encode sets and the parser every result is cross-checked with.
|
|
286
|
+
- [RFC 3986](https://datatracker.ietf.org/doc/html/rfc3986) by Berners-Lee, Fielding and Masinter, and [RFC 3987](https://datatracker.ietf.org/doc/html/rfc3987) by Duerst and Suignard.
|
|
287
|
+
- [Rolldown](https://rolldown.rs), [Oxc](https://oxc.rs), [Vitest](https://vitest.dev), [Bun](https://bun.sh) and [TypeScript](https://www.typescriptlang.org).
|
|
288
|
+
|
|
289
|
+
## License
|
|
290
|
+
|
|
291
|
+
MIT. Transliteration values are derived from simov/slugify and sindresorhus/transliterate (both MIT).
|
package/dist/index.d.mts
ADDED
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
import { a as TransliterationTable, i as LocaleId, n as LatinLocaleId, r as Locale } from "./shared/types-C1iMvUXh.mjs";
|
|
2
|
+
//#region src/slug/bidi.d.ts
|
|
3
|
+
export declare function isBidiSafeComponent(text: string): boolean;
|
|
4
|
+
//#endregion
|
|
5
|
+
//#region src/slug/decamelize.d.ts
|
|
6
|
+
export declare function decamelize(input: string): string;
|
|
7
|
+
//#endregion
|
|
8
|
+
//#region src/slug/scripts.d.ts
|
|
9
|
+
type ScriptRestriction = "single" | "highly-restrictive" | "moderately-restrictive" | "any";
|
|
10
|
+
interface ScriptCheck {
|
|
11
|
+
readonly ok: boolean;
|
|
12
|
+
readonly scripts: readonly string[];
|
|
13
|
+
}
|
|
14
|
+
export declare function detectScripts(text: string): string[];
|
|
15
|
+
export declare function checkScripts(text: string, level?: ScriptRestriction): ScriptCheck;
|
|
16
|
+
//#endregion
|
|
17
|
+
//#region src/slug/options.d.ts
|
|
18
|
+
interface SlugifyOptions {
|
|
19
|
+
readonly separator?: string;
|
|
20
|
+
readonly lowercase?: boolean;
|
|
21
|
+
readonly unicode?: boolean;
|
|
22
|
+
readonly locale?: LatinLocaleId | Locale;
|
|
23
|
+
readonly transliterate?: boolean | readonly TransliterationTable[];
|
|
24
|
+
readonly decamelize?: boolean;
|
|
25
|
+
readonly replacements?: ReadonlyArray<readonly [string, string]>;
|
|
26
|
+
readonly remove?: RegExp | false;
|
|
27
|
+
readonly preserveCharacters?: readonly string[];
|
|
28
|
+
readonly preserveLeadingUnderscore?: boolean;
|
|
29
|
+
readonly preserveTrailingSeparator?: boolean;
|
|
30
|
+
readonly maxLength?: number;
|
|
31
|
+
readonly scripts?: ScriptRestriction;
|
|
32
|
+
readonly bidi?: "allow" | "encode" | "throw";
|
|
33
|
+
}
|
|
34
|
+
type IsSlugOptions = Pick<SlugifyOptions, "separator" | "lowercase" | "unicode" | "preserveCharacters" | "preserveLeadingUnderscore" | "preserveTrailingSeparator" | "maxLength" | "scripts" | "bidi">;
|
|
35
|
+
//#endregion
|
|
36
|
+
//#region src/slug/is-slug.d.ts
|
|
37
|
+
export declare function isSlug(input: string, options?: IsSlugOptions): boolean;
|
|
38
|
+
//#endregion
|
|
39
|
+
//#region src/slug/slugger.d.ts
|
|
40
|
+
interface Slugger {
|
|
41
|
+
(input: string, options?: SlugifyOptions): string;
|
|
42
|
+
reset(): void;
|
|
43
|
+
has(slug: string): boolean;
|
|
44
|
+
reserve(slug: string): void;
|
|
45
|
+
}
|
|
46
|
+
export declare function createSlugger(defaults?: SlugifyOptions): Slugger;
|
|
47
|
+
export declare const slugifyWithCounter: (defaults?: SlugifyOptions) => Slugger;
|
|
48
|
+
//#endregion
|
|
49
|
+
//#region src/slug/slugify.d.ts
|
|
50
|
+
export declare function slugify(input: string, options?: SlugifyOptions): string;
|
|
51
|
+
//#endregion
|
|
52
|
+
//#region src/slug/truncate.d.ts
|
|
53
|
+
export declare function truncateSlug(slug: string, maxLength: number, separator?: string): string;
|
|
54
|
+
//#endregion
|
|
55
|
+
export type { IsSlugOptions, LatinLocaleId, Locale, LocaleId, ScriptCheck, ScriptRestriction, Slugger, SlugifyOptions, TransliterationTable };
|