slugen 0.1.1
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 +21 -0
- package/README.md +211 -0
- package/dist/index.d.mts +17 -0
- package/dist/index.d.ts +17 -0
- package/dist/index.js +173 -0
- package/dist/index.js.map +1 -0
- package/dist/index.mjs +170 -0
- package/dist/index.mjs.map +1 -0
- package/package.json +51 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Kable
|
|
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.
|
package/README.md
ADDED
|
@@ -0,0 +1,211 @@
|
|
|
1
|
+
# slugen
|
|
2
|
+
|
|
3
|
+
A modern, fast slug generator for JavaScript and TypeScript.
|
|
4
|
+
|
|
5
|
+
Generate URL-friendly slugs, usernames, SEO-friendly links, or safely convert text into clean identifiers.
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
## Features
|
|
9
|
+
|
|
10
|
+
- **Multi-locale support** — de, fr, tr, es, and more
|
|
11
|
+
- **Accurate normalization** of accented and language-specific characters
|
|
12
|
+
- **Smart symbol handling** with semantic replacements instead of blind removal
|
|
13
|
+
- **High performance**, suitable for batch and large-scale processing
|
|
14
|
+
- **Customizable separators** (`-`, `_`, `.`)
|
|
15
|
+
- **Deterministic output** with comprehensive test coverage
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
## Installation
|
|
19
|
+
|
|
20
|
+
```bash
|
|
21
|
+
npm install slugen
|
|
22
|
+
```
|
|
23
|
+
or
|
|
24
|
+
|
|
25
|
+
```bash
|
|
26
|
+
pnpm add slugen
|
|
27
|
+
```
|
|
28
|
+
|
|
29
|
+
## Quick Start
|
|
30
|
+
|
|
31
|
+
```ts
|
|
32
|
+
import { slugen } from "slugen";
|
|
33
|
+
|
|
34
|
+
slugen("Hello World");
|
|
35
|
+
// → "hello-world"
|
|
36
|
+
|
|
37
|
+
slugen("Istanbul's Best Coffee!");
|
|
38
|
+
// → "istanbuls-best-coffee"
|
|
39
|
+
```
|
|
40
|
+
|
|
41
|
+
## With Options
|
|
42
|
+
```ts
|
|
43
|
+
import { slugen } from "slugen";
|
|
44
|
+
|
|
45
|
+
slugen("Hello World", {
|
|
46
|
+
locale: "fr", // or ["fr", "de"]
|
|
47
|
+
separator: "-",
|
|
48
|
+
lowercase: true,
|
|
49
|
+
symbols: true,
|
|
50
|
+
customReplacements: {
|
|
51
|
+
"&": "and",
|
|
52
|
+
},
|
|
53
|
+
});
|
|
54
|
+
// hello-world
|
|
55
|
+
```
|
|
56
|
+
|
|
57
|
+
## Separator
|
|
58
|
+
|
|
59
|
+
The default separator is -.
|
|
60
|
+
You can also use `_` or `.`
|
|
61
|
+
|
|
62
|
+
```ts
|
|
63
|
+
slugen("Hello World", { separator: "-" });
|
|
64
|
+
// hello-world
|
|
65
|
+
slugen("Hello World", { separator: "_" });
|
|
66
|
+
// hello_world
|
|
67
|
+
slugen("Hello World", { separator: "." });
|
|
68
|
+
// hello.world
|
|
69
|
+
```
|
|
70
|
+
The separator must be a single character.
|
|
71
|
+
|
|
72
|
+
## Locale Support
|
|
73
|
+
|
|
74
|
+
By default, slugen uses Unicode normalization and removes diacritics.
|
|
75
|
+
You can optionally provide a locale for better language-specific handling.
|
|
76
|
+
|
|
77
|
+
```ts
|
|
78
|
+
import { slugen } from "slugen";
|
|
79
|
+
|
|
80
|
+
slugen("Café français", { locale: "fr" });
|
|
81
|
+
// → "cafe-francais"
|
|
82
|
+
slugen("Español México", { locale: "es" });
|
|
83
|
+
// → "espanol-mexico"
|
|
84
|
+
slugen("İstanbul Boğazı", { locale: "tr" });
|
|
85
|
+
// istanbul-bogazi
|
|
86
|
+
slugen("Straße Größe", { locale: "de" });
|
|
87
|
+
// strasse-grosse
|
|
88
|
+
|
|
89
|
+
```
|
|
90
|
+
### Multiple locales (merged)
|
|
91
|
+
```ts
|
|
92
|
+
slugen("İstanbul Straße Español", { locale: ["tr", "de", "es"]});
|
|
93
|
+
// istanbul-strasse-espanol
|
|
94
|
+
```
|
|
95
|
+
Locales are applied in order and safely merged.
|
|
96
|
+
|
|
97
|
+
## Symbol Handling
|
|
98
|
+
|
|
99
|
+
slugen categorizes symbols into three groups:
|
|
100
|
+
|
|
101
|
+
### Examples:
|
|
102
|
+
```ts
|
|
103
|
+
slugen("foo & bar");
|
|
104
|
+
// foo-and-bar
|
|
105
|
+
slugen("email@test.com");
|
|
106
|
+
// email-at-test-com
|
|
107
|
+
slugen("50€");
|
|
108
|
+
// 50-euro
|
|
109
|
+
slugen("foo & bar");
|
|
110
|
+
// foo-and-bar
|
|
111
|
+
slugen("100$ price");
|
|
112
|
+
// 100-dollar-price
|
|
113
|
+
slugen("50% off");
|
|
114
|
+
// 50-percent-off
|
|
115
|
+
```
|
|
116
|
+
|
|
117
|
+
### Disable Symbols
|
|
118
|
+
```ts
|
|
119
|
+
slugen("foo & bar", { symbols: false });
|
|
120
|
+
// foo-bar
|
|
121
|
+
```
|
|
122
|
+
|
|
123
|
+
## Custom Replacements
|
|
124
|
+
|
|
125
|
+
```ts
|
|
126
|
+
slugen("React & Vue", {
|
|
127
|
+
customReplacements: {
|
|
128
|
+
"&": "and",
|
|
129
|
+
"React": "react-js"
|
|
130
|
+
}
|
|
131
|
+
});
|
|
132
|
+
// react-js-and-vue
|
|
133
|
+
|
|
134
|
+
slugen("C++ Programming", {
|
|
135
|
+
customReplacements: {
|
|
136
|
+
"++": "plus-plus"
|
|
137
|
+
}
|
|
138
|
+
});
|
|
139
|
+
// c-plus-plus-programming
|
|
140
|
+
```
|
|
141
|
+
|
|
142
|
+
|
|
143
|
+
## Real-World Examples
|
|
144
|
+
```ts
|
|
145
|
+
slugen("How to Learn JavaScript in 2025?");
|
|
146
|
+
// how-to-learn-javascript-in-2025
|
|
147
|
+
slugen("React vs Vue: Which is Better?");
|
|
148
|
+
// react-vs-vue-which-is-better
|
|
149
|
+
slugen("iPhone 15 Pro Max");
|
|
150
|
+
// iphone-15-pro-max
|
|
151
|
+
slugen("Samsung 55\" 4K Smart TV");
|
|
152
|
+
// samsung-55-4k-smart-tv
|
|
153
|
+
```
|
|
154
|
+
|
|
155
|
+
### Usernames:
|
|
156
|
+
|
|
157
|
+
```ts
|
|
158
|
+
slugen("John Doe", { separator: "_" });
|
|
159
|
+
// john_doe
|
|
160
|
+
|
|
161
|
+
slugen("jane.smith@example", { separator: "_" });
|
|
162
|
+
// jane_smith_at_example
|
|
163
|
+
```
|
|
164
|
+
|
|
165
|
+
### Filenames:
|
|
166
|
+
|
|
167
|
+
```ts
|
|
168
|
+
slugen("Project Report 2024.pdf", { separator: "_" });
|
|
169
|
+
// project_report_2024_pdf
|
|
170
|
+
```
|
|
171
|
+
|
|
172
|
+
|
|
173
|
+
## API Reference
|
|
174
|
+
|
|
175
|
+
### ``slugen(text, options?)``
|
|
176
|
+
|
|
177
|
+
Generates a slug from the given text.
|
|
178
|
+
|
|
179
|
+
**Parameters:**
|
|
180
|
+
- `text` (string) - Text to convert into a slug
|
|
181
|
+
- `options` (object, optional) - Configuration options
|
|
182
|
+
|
|
183
|
+
**Options:**
|
|
184
|
+
|
|
185
|
+
| Option | Type | Default | Description |
|
|
186
|
+
| -------------------- | ------------------------ | ----------- | ----------------------------------------------- |
|
|
187
|
+
| `locale` | `string \| string[]` | `undefined` | Language support (e.g., `"tr"`, `"de"`, `"fr"`) |
|
|
188
|
+
| `separator` | `"-" \| "_" \| "."` | `"-"` | Character to separate words |
|
|
189
|
+
| `lowercase` | `boolean` | `true` | Convert to lowercase |
|
|
190
|
+
| `symbols` | `boolean` | `true` | Enable symbol normalization |
|
|
191
|
+
| `customReplacements` | `Record<string, string>` | `{}` | Custom character replacements |
|
|
192
|
+
|
|
193
|
+
**Returns:** `string` - Generated slug
|
|
194
|
+
|
|
195
|
+
|
|
196
|
+
## Design Notes
|
|
197
|
+
|
|
198
|
+
- ``applySymbols`` only normalizes text; it does not decide slug structure
|
|
199
|
+
- A single regex pass handles final slug normalization
|
|
200
|
+
- Locale and separator behavior is deterministic
|
|
201
|
+
- Minimal API surface, easy to extend
|
|
202
|
+
|
|
203
|
+
|
|
204
|
+
## Contributing
|
|
205
|
+
|
|
206
|
+
Contributions are welcome! Please open an issue or submit a pull request on GitHub.
|
|
207
|
+
|
|
208
|
+
|
|
209
|
+
## License
|
|
210
|
+
MIT
|
|
211
|
+
|
package/dist/index.d.mts
ADDED
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
interface SlugenOptions {
|
|
2
|
+
locale?: string | string[];
|
|
3
|
+
separator?: string;
|
|
4
|
+
lowercase?: boolean;
|
|
5
|
+
symbols?: boolean;
|
|
6
|
+
customReplacements?: Record<string, string>;
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
declare function slugen(input: string, options?: SlugenOptions): string;
|
|
10
|
+
|
|
11
|
+
type LocaleMap = Record<string, string>;
|
|
12
|
+
/**
|
|
13
|
+
* Locale definitions
|
|
14
|
+
*/
|
|
15
|
+
declare const locales: Record<string, LocaleMap>;
|
|
16
|
+
|
|
17
|
+
export { type SlugenOptions, locales, slugen };
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
interface SlugenOptions {
|
|
2
|
+
locale?: string | string[];
|
|
3
|
+
separator?: string;
|
|
4
|
+
lowercase?: boolean;
|
|
5
|
+
symbols?: boolean;
|
|
6
|
+
customReplacements?: Record<string, string>;
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
declare function slugen(input: string, options?: SlugenOptions): string;
|
|
10
|
+
|
|
11
|
+
type LocaleMap = Record<string, string>;
|
|
12
|
+
/**
|
|
13
|
+
* Locale definitions
|
|
14
|
+
*/
|
|
15
|
+
declare const locales: Record<string, LocaleMap>;
|
|
16
|
+
|
|
17
|
+
export { type SlugenOptions, locales, slugen };
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,173 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
// src/locales.ts
|
|
4
|
+
var locales = {
|
|
5
|
+
de: {
|
|
6
|
+
\u00C4: "a",
|
|
7
|
+
\u00E4: "a",
|
|
8
|
+
\u00D6: "o",
|
|
9
|
+
\u00F6: "o",
|
|
10
|
+
\u00DC: "u",
|
|
11
|
+
\u00FC: "u",
|
|
12
|
+
\u00DF: "ss"
|
|
13
|
+
},
|
|
14
|
+
fr: {
|
|
15
|
+
\u00C0: "a",
|
|
16
|
+
\u00C1: "a",
|
|
17
|
+
\u00C2: "a",
|
|
18
|
+
\u00C3: "a",
|
|
19
|
+
\u00C4: "a",
|
|
20
|
+
\u00C5: "a",
|
|
21
|
+
\u00E0: "a",
|
|
22
|
+
\u00E1: "a",
|
|
23
|
+
\u00E2: "a",
|
|
24
|
+
\u00E3: "a",
|
|
25
|
+
\u00E4: "a",
|
|
26
|
+
\u00E5: "a",
|
|
27
|
+
\u00C7: "c",
|
|
28
|
+
\u00E7: "c",
|
|
29
|
+
\u00C8: "e",
|
|
30
|
+
\u00C9: "e",
|
|
31
|
+
\u00CA: "e",
|
|
32
|
+
\u00CB: "e",
|
|
33
|
+
\u00E8: "e",
|
|
34
|
+
\u00E9: "e",
|
|
35
|
+
\u00EA: "e",
|
|
36
|
+
\u00EB: "e",
|
|
37
|
+
\u0152: "oe",
|
|
38
|
+
\u0153: "oe"
|
|
39
|
+
},
|
|
40
|
+
es: {
|
|
41
|
+
\u00D1: "n",
|
|
42
|
+
\u00F1: "n"
|
|
43
|
+
},
|
|
44
|
+
tr: {
|
|
45
|
+
\u0130: "i",
|
|
46
|
+
I: "i",
|
|
47
|
+
\u0131: "i",
|
|
48
|
+
\u015E: "s",
|
|
49
|
+
\u015F: "s",
|
|
50
|
+
\u011E: "g",
|
|
51
|
+
\u011F: "g",
|
|
52
|
+
\u00D6: "o",
|
|
53
|
+
\u00F6: "o",
|
|
54
|
+
\u00DC: "u",
|
|
55
|
+
\u00FC: "u",
|
|
56
|
+
\u00C7: "c",
|
|
57
|
+
\u00E7: "c"
|
|
58
|
+
},
|
|
59
|
+
pl: {
|
|
60
|
+
\u0141: "l",
|
|
61
|
+
\u0142: "l",
|
|
62
|
+
\u015A: "s",
|
|
63
|
+
\u015B: "s",
|
|
64
|
+
\u017B: "z",
|
|
65
|
+
\u017C: "z"
|
|
66
|
+
},
|
|
67
|
+
en: {}
|
|
68
|
+
};
|
|
69
|
+
var localeCache = /* @__PURE__ */ new Map();
|
|
70
|
+
function mergeLocales(localeKeys) {
|
|
71
|
+
const merged = {};
|
|
72
|
+
for (const key of localeKeys) {
|
|
73
|
+
const locale = locales[key];
|
|
74
|
+
if (!locale) continue;
|
|
75
|
+
Object.assign(merged, locale);
|
|
76
|
+
}
|
|
77
|
+
return merged;
|
|
78
|
+
}
|
|
79
|
+
function applyLocale(text, locale) {
|
|
80
|
+
if (!locale) return text;
|
|
81
|
+
const keys = Array.isArray(locale) ? locale : [locale];
|
|
82
|
+
const cacheKey = keys.sort().join("+");
|
|
83
|
+
let cached = localeCache.get(cacheKey);
|
|
84
|
+
if (!cached) {
|
|
85
|
+
const map = mergeLocales(keys);
|
|
86
|
+
if (Object.keys(map).length === 0) {
|
|
87
|
+
localeCache.set(cacheKey, { map, regex: /$^/ });
|
|
88
|
+
return text;
|
|
89
|
+
}
|
|
90
|
+
const pattern = new RegExp(`[${Object.keys(map).join("")}]`, "g");
|
|
91
|
+
cached = { map, regex: pattern };
|
|
92
|
+
localeCache.set(cacheKey, cached);
|
|
93
|
+
}
|
|
94
|
+
return text.replace(cached.regex, (char) => cached.map[char] ?? char);
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
// src/symbols.ts
|
|
98
|
+
var symbols = {
|
|
99
|
+
// Logical / textual (semantic)
|
|
100
|
+
"&": " and ",
|
|
101
|
+
"|": " or ",
|
|
102
|
+
"@": " at ",
|
|
103
|
+
"%": " percent ",
|
|
104
|
+
"+": " plus ",
|
|
105
|
+
"=": " equals ",
|
|
106
|
+
// Currency (semantic)
|
|
107
|
+
$: " dollar ",
|
|
108
|
+
"\u20AC": " euro ",
|
|
109
|
+
"\xA3": " pound ",
|
|
110
|
+
"\u20BA": " lira ",
|
|
111
|
+
"\xA5": " yen ",
|
|
112
|
+
"\u20B9": " rupee ",
|
|
113
|
+
// Separators
|
|
114
|
+
".": " ",
|
|
115
|
+
"/": " ",
|
|
116
|
+
"\\": " ",
|
|
117
|
+
_: " ",
|
|
118
|
+
// Noise (strip)
|
|
119
|
+
"<": "",
|
|
120
|
+
">": "",
|
|
121
|
+
"~": "",
|
|
122
|
+
"^": "",
|
|
123
|
+
"?": "",
|
|
124
|
+
"!": "",
|
|
125
|
+
",": "",
|
|
126
|
+
":": "",
|
|
127
|
+
";": "",
|
|
128
|
+
"'": "",
|
|
129
|
+
'"': "",
|
|
130
|
+
"`": "",
|
|
131
|
+
"*": "",
|
|
132
|
+
"#": "",
|
|
133
|
+
"(": "",
|
|
134
|
+
")": "",
|
|
135
|
+
"[": "",
|
|
136
|
+
"]": "",
|
|
137
|
+
"{": "",
|
|
138
|
+
"}": ""
|
|
139
|
+
};
|
|
140
|
+
var escapeRegex = (char) => char.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
141
|
+
var BASE_SYMBOL_REGEX = new RegExp(`[${Object.keys(symbols).map(escapeRegex).join("")}]`, "g");
|
|
142
|
+
function applySymbols(text, custom) {
|
|
143
|
+
const map = custom ? { ...symbols, ...custom } : symbols;
|
|
144
|
+
const regex = map === symbols ? BASE_SYMBOL_REGEX : new RegExp(`[${Object.keys(map).map(escapeRegex).join("")}]`, "g");
|
|
145
|
+
return text.replace(regex, (char) => map[char] ?? "");
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
// src/slugen.ts
|
|
149
|
+
function escapeRegex2(char) {
|
|
150
|
+
return char.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
151
|
+
}
|
|
152
|
+
function slugen(input, options = {}) {
|
|
153
|
+
if (!input) return "";
|
|
154
|
+
const separator = options.separator ?? "-";
|
|
155
|
+
const safeSeparator = escapeRegex2(separator);
|
|
156
|
+
let text = input;
|
|
157
|
+
if (options.symbols !== false) {
|
|
158
|
+
text = applySymbols(text, options.customReplacements);
|
|
159
|
+
}
|
|
160
|
+
text = applyLocale(text, options.locale);
|
|
161
|
+
text = text.normalize("NFD").replace(/[\u0300-\u036f]/g, "");
|
|
162
|
+
text = text.replace(/[^a-zA-Z0-9]+/g, separator);
|
|
163
|
+
text = text.replace(new RegExp(`^${safeSeparator}+|${safeSeparator}+$`, "g"), "");
|
|
164
|
+
if (options.lowercase !== false) {
|
|
165
|
+
text = text.toLowerCase();
|
|
166
|
+
}
|
|
167
|
+
return text;
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
exports.locales = locales;
|
|
171
|
+
exports.slugen = slugen;
|
|
172
|
+
//# sourceMappingURL=index.js.map
|
|
173
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/locales.ts","../src/symbols.ts","../src/slugen.ts"],"names":["escapeRegex"],"mappings":"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","file":"index.js","sourcesContent":["type LocaleMap = Record<string, string>;\r\n\r\n/**\r\n * Locale definitions\r\n */\r\nexport const locales: Record<string, LocaleMap> = {\r\n de: {\r\n Ä: \"a\",\r\n ä: \"a\",\r\n Ö: \"o\",\r\n ö: \"o\",\r\n Ü: \"u\",\r\n ü: \"u\",\r\n ß: \"ss\",\r\n },\r\n\r\n fr: {\r\n À: \"a\",\r\n Á: \"a\",\r\n Â: \"a\",\r\n Ã: \"a\",\r\n Ä: \"a\",\r\n Å: \"a\",\r\n à: \"a\",\r\n á: \"a\",\r\n â: \"a\",\r\n ã: \"a\",\r\n ä: \"a\",\r\n å: \"a\",\r\n Ç: \"c\",\r\n ç: \"c\",\r\n È: \"e\",\r\n É: \"e\",\r\n Ê: \"e\",\r\n Ë: \"e\",\r\n è: \"e\",\r\n é: \"e\",\r\n ê: \"e\",\r\n ë: \"e\",\r\n Œ: \"oe\",\r\n œ: \"oe\",\r\n },\r\n\r\n es: {\r\n Ñ: \"n\",\r\n ñ: \"n\",\r\n },\r\n\r\n tr: {\r\n İ: \"i\",\r\n I: \"i\",\r\n ı: \"i\",\r\n Ş: \"s\",\r\n ş: \"s\",\r\n Ğ: \"g\",\r\n ğ: \"g\",\r\n Ö: \"o\",\r\n ö: \"o\",\r\n Ü: \"u\",\r\n ü: \"u\",\r\n Ç: \"c\",\r\n ç: \"c\",\r\n },\r\n\r\n pl: {\r\n Ł: \"l\",\r\n ł: \"l\",\r\n Ś: \"s\",\r\n ś: \"s\",\r\n Ż: \"z\",\r\n ż: \"z\",\r\n },\r\n\r\n en: {},\r\n};\r\n\r\n/**\r\n * Cache: localeKey -> { map, regex }\r\n */\r\nconst localeCache = new Map<string, { map: LocaleMap; regex: RegExp }>();\r\n\r\n/**\r\n * Merge multiple locales into a single map\r\n */\r\nfunction mergeLocales(localeKeys: string[]): LocaleMap {\r\n const merged: LocaleMap = {};\r\n\r\n for (const key of localeKeys) {\r\n const locale = locales[key];\r\n if (!locale) continue;\r\n Object.assign(merged, locale);\r\n }\r\n\r\n return merged;\r\n}\r\n\r\n/**\r\n * Apply locale replacements\r\n *\r\n * Supports:\r\n * - 'fr'\r\n * - ['fr', 'de']\r\n */\r\nexport function applyLocale(text: string, locale?: string | string[]): string {\r\n if (!locale) return text;\r\n\r\n const keys = Array.isArray(locale) ? locale : [locale];\r\n const cacheKey = keys.sort().join(\"+\");\r\n\r\n let cached = localeCache.get(cacheKey);\r\n\r\n if (!cached) {\r\n const map = mergeLocales(keys);\r\n\r\n if (Object.keys(map).length === 0) {\r\n localeCache.set(cacheKey, { map, regex: /$^/ });\r\n return text;\r\n }\r\n\r\n const pattern = new RegExp(`[${Object.keys(map).join(\"\")}]`, \"g\");\r\n\r\n cached = { map, regex: pattern };\r\n localeCache.set(cacheKey, cached);\r\n }\r\n\r\n return text.replace(cached.regex, (char) => cached!.map[char] ?? char);\r\n}\r\n","/**\r\n * Common symbols and their slug-friendly replacements\r\n * Policy:\r\n * - semantic symbols => \" word \"\r\n * - separators => \" \"\r\n * - noise => \"\"\r\n */\r\n\r\nexport const symbols: Record<string, string> = {\r\n // Logical / textual (semantic)\r\n \"&\": \" and \",\r\n \"|\": \" or \",\r\n \"@\": \" at \",\r\n \"%\": \" percent \",\r\n \"+\": \" plus \",\r\n \"=\": \" equals \",\r\n\r\n // Currency (semantic)\r\n $: \" dollar \",\r\n \"€\": \" euro \",\r\n \"£\": \" pound \",\r\n \"₺\": \" lira \",\r\n \"¥\": \" yen \",\r\n \"₹\": \" rupee \",\r\n\r\n // Separators\r\n \".\": \" \",\r\n \"/\": \" \",\r\n \"\\\\\": \" \",\r\n _: \" \",\r\n\r\n // Noise (strip)\r\n \"<\": \"\",\r\n \">\": \"\",\r\n \"~\": \"\",\r\n \"^\": \"\",\r\n \"?\": \"\",\r\n \"!\": \"\",\r\n \",\": \"\",\r\n \":\": \"\",\r\n \";\": \"\",\r\n \"'\": \"\",\r\n '\"': \"\",\r\n \"`\": \"\",\r\n \"*\": \"\",\r\n \"#\": \"\",\r\n \"(\": \"\",\r\n \")\": \"\",\r\n \"[\": \"\",\r\n \"]\": \"\",\r\n \"{\": \"\",\r\n \"}\": \"\",\r\n};\r\n\r\n/** Escape regex special chars safely */\r\nconst escapeRegex = (char: string) => char.replace(/[.*+?^${}()|[\\]\\\\]/g, \"\\\\$&\");\r\n\r\nconst BASE_SYMBOL_REGEX = new RegExp(`[${Object.keys(symbols).map(escapeRegex).join(\"\")}]`, \"g\");\r\n\r\nexport function applySymbols(text: string, custom?: Record<string, string>) {\r\n const map = custom ? { ...symbols, ...custom } : symbols;\r\n const regex = map === symbols ? BASE_SYMBOL_REGEX : new RegExp(`[${Object.keys(map).map(escapeRegex).join(\"\")}]`, \"g\");\r\n\r\n return text.replace(regex, (char) => map[char] ?? \"\");\r\n}\r\n","import { SlugenOptions } from \"./types\";\r\nimport { applyLocale } from \"./locales\";\r\nimport { applySymbols } from \"./symbols\";\r\n\r\nfunction escapeRegex(char: string) {\r\n return char.replace(/[.*+?^${}()|[\\]\\\\]/g, \"\\\\$&\");\r\n}\r\n\r\nexport function slugen(input: string, options: SlugenOptions = {}): string {\r\n if (!input) return \"\";\r\n\r\n const separator = options.separator ?? \"-\";\r\n const safeSeparator = escapeRegex(separator);\r\n let text = input;\r\n\r\n // Symbols (default true)\r\n if (options.symbols !== false) {\r\n text = applySymbols(text, options.customReplacements);\r\n }\r\n\r\n // Locale\r\n text = applyLocale(text, options.locale);\r\n\r\n // Normalize & remove diacritics\r\n text = text.normalize(\"NFD\").replace(/[\\u0300-\\u036f]/g, \"\");\r\n\r\n // Replace non-alphanumeric with separator\r\n text = text.replace(/[^a-zA-Z0-9]+/g, separator);\r\n\r\n // Trim separator (SAFE)\r\n text = text.replace(new RegExp(`^${safeSeparator}+|${safeSeparator}+$`, \"g\"), \"\");\r\n\r\n // Lowercase (default true)\r\n if (options.lowercase !== false) {\r\n text = text.toLowerCase();\r\n }\r\n\r\n return text;\r\n}\r\n"]}
|
package/dist/index.mjs
ADDED
|
@@ -0,0 +1,170 @@
|
|
|
1
|
+
// src/locales.ts
|
|
2
|
+
var locales = {
|
|
3
|
+
de: {
|
|
4
|
+
\u00C4: "a",
|
|
5
|
+
\u00E4: "a",
|
|
6
|
+
\u00D6: "o",
|
|
7
|
+
\u00F6: "o",
|
|
8
|
+
\u00DC: "u",
|
|
9
|
+
\u00FC: "u",
|
|
10
|
+
\u00DF: "ss"
|
|
11
|
+
},
|
|
12
|
+
fr: {
|
|
13
|
+
\u00C0: "a",
|
|
14
|
+
\u00C1: "a",
|
|
15
|
+
\u00C2: "a",
|
|
16
|
+
\u00C3: "a",
|
|
17
|
+
\u00C4: "a",
|
|
18
|
+
\u00C5: "a",
|
|
19
|
+
\u00E0: "a",
|
|
20
|
+
\u00E1: "a",
|
|
21
|
+
\u00E2: "a",
|
|
22
|
+
\u00E3: "a",
|
|
23
|
+
\u00E4: "a",
|
|
24
|
+
\u00E5: "a",
|
|
25
|
+
\u00C7: "c",
|
|
26
|
+
\u00E7: "c",
|
|
27
|
+
\u00C8: "e",
|
|
28
|
+
\u00C9: "e",
|
|
29
|
+
\u00CA: "e",
|
|
30
|
+
\u00CB: "e",
|
|
31
|
+
\u00E8: "e",
|
|
32
|
+
\u00E9: "e",
|
|
33
|
+
\u00EA: "e",
|
|
34
|
+
\u00EB: "e",
|
|
35
|
+
\u0152: "oe",
|
|
36
|
+
\u0153: "oe"
|
|
37
|
+
},
|
|
38
|
+
es: {
|
|
39
|
+
\u00D1: "n",
|
|
40
|
+
\u00F1: "n"
|
|
41
|
+
},
|
|
42
|
+
tr: {
|
|
43
|
+
\u0130: "i",
|
|
44
|
+
I: "i",
|
|
45
|
+
\u0131: "i",
|
|
46
|
+
\u015E: "s",
|
|
47
|
+
\u015F: "s",
|
|
48
|
+
\u011E: "g",
|
|
49
|
+
\u011F: "g",
|
|
50
|
+
\u00D6: "o",
|
|
51
|
+
\u00F6: "o",
|
|
52
|
+
\u00DC: "u",
|
|
53
|
+
\u00FC: "u",
|
|
54
|
+
\u00C7: "c",
|
|
55
|
+
\u00E7: "c"
|
|
56
|
+
},
|
|
57
|
+
pl: {
|
|
58
|
+
\u0141: "l",
|
|
59
|
+
\u0142: "l",
|
|
60
|
+
\u015A: "s",
|
|
61
|
+
\u015B: "s",
|
|
62
|
+
\u017B: "z",
|
|
63
|
+
\u017C: "z"
|
|
64
|
+
},
|
|
65
|
+
en: {}
|
|
66
|
+
};
|
|
67
|
+
var localeCache = /* @__PURE__ */ new Map();
|
|
68
|
+
function mergeLocales(localeKeys) {
|
|
69
|
+
const merged = {};
|
|
70
|
+
for (const key of localeKeys) {
|
|
71
|
+
const locale = locales[key];
|
|
72
|
+
if (!locale) continue;
|
|
73
|
+
Object.assign(merged, locale);
|
|
74
|
+
}
|
|
75
|
+
return merged;
|
|
76
|
+
}
|
|
77
|
+
function applyLocale(text, locale) {
|
|
78
|
+
if (!locale) return text;
|
|
79
|
+
const keys = Array.isArray(locale) ? locale : [locale];
|
|
80
|
+
const cacheKey = keys.sort().join("+");
|
|
81
|
+
let cached = localeCache.get(cacheKey);
|
|
82
|
+
if (!cached) {
|
|
83
|
+
const map = mergeLocales(keys);
|
|
84
|
+
if (Object.keys(map).length === 0) {
|
|
85
|
+
localeCache.set(cacheKey, { map, regex: /$^/ });
|
|
86
|
+
return text;
|
|
87
|
+
}
|
|
88
|
+
const pattern = new RegExp(`[${Object.keys(map).join("")}]`, "g");
|
|
89
|
+
cached = { map, regex: pattern };
|
|
90
|
+
localeCache.set(cacheKey, cached);
|
|
91
|
+
}
|
|
92
|
+
return text.replace(cached.regex, (char) => cached.map[char] ?? char);
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
// src/symbols.ts
|
|
96
|
+
var symbols = {
|
|
97
|
+
// Logical / textual (semantic)
|
|
98
|
+
"&": " and ",
|
|
99
|
+
"|": " or ",
|
|
100
|
+
"@": " at ",
|
|
101
|
+
"%": " percent ",
|
|
102
|
+
"+": " plus ",
|
|
103
|
+
"=": " equals ",
|
|
104
|
+
// Currency (semantic)
|
|
105
|
+
$: " dollar ",
|
|
106
|
+
"\u20AC": " euro ",
|
|
107
|
+
"\xA3": " pound ",
|
|
108
|
+
"\u20BA": " lira ",
|
|
109
|
+
"\xA5": " yen ",
|
|
110
|
+
"\u20B9": " rupee ",
|
|
111
|
+
// Separators
|
|
112
|
+
".": " ",
|
|
113
|
+
"/": " ",
|
|
114
|
+
"\\": " ",
|
|
115
|
+
_: " ",
|
|
116
|
+
// Noise (strip)
|
|
117
|
+
"<": "",
|
|
118
|
+
">": "",
|
|
119
|
+
"~": "",
|
|
120
|
+
"^": "",
|
|
121
|
+
"?": "",
|
|
122
|
+
"!": "",
|
|
123
|
+
",": "",
|
|
124
|
+
":": "",
|
|
125
|
+
";": "",
|
|
126
|
+
"'": "",
|
|
127
|
+
'"': "",
|
|
128
|
+
"`": "",
|
|
129
|
+
"*": "",
|
|
130
|
+
"#": "",
|
|
131
|
+
"(": "",
|
|
132
|
+
")": "",
|
|
133
|
+
"[": "",
|
|
134
|
+
"]": "",
|
|
135
|
+
"{": "",
|
|
136
|
+
"}": ""
|
|
137
|
+
};
|
|
138
|
+
var escapeRegex = (char) => char.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
139
|
+
var BASE_SYMBOL_REGEX = new RegExp(`[${Object.keys(symbols).map(escapeRegex).join("")}]`, "g");
|
|
140
|
+
function applySymbols(text, custom) {
|
|
141
|
+
const map = custom ? { ...symbols, ...custom } : symbols;
|
|
142
|
+
const regex = map === symbols ? BASE_SYMBOL_REGEX : new RegExp(`[${Object.keys(map).map(escapeRegex).join("")}]`, "g");
|
|
143
|
+
return text.replace(regex, (char) => map[char] ?? "");
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
// src/slugen.ts
|
|
147
|
+
function escapeRegex2(char) {
|
|
148
|
+
return char.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
149
|
+
}
|
|
150
|
+
function slugen(input, options = {}) {
|
|
151
|
+
if (!input) return "";
|
|
152
|
+
const separator = options.separator ?? "-";
|
|
153
|
+
const safeSeparator = escapeRegex2(separator);
|
|
154
|
+
let text = input;
|
|
155
|
+
if (options.symbols !== false) {
|
|
156
|
+
text = applySymbols(text, options.customReplacements);
|
|
157
|
+
}
|
|
158
|
+
text = applyLocale(text, options.locale);
|
|
159
|
+
text = text.normalize("NFD").replace(/[\u0300-\u036f]/g, "");
|
|
160
|
+
text = text.replace(/[^a-zA-Z0-9]+/g, separator);
|
|
161
|
+
text = text.replace(new RegExp(`^${safeSeparator}+|${safeSeparator}+$`, "g"), "");
|
|
162
|
+
if (options.lowercase !== false) {
|
|
163
|
+
text = text.toLowerCase();
|
|
164
|
+
}
|
|
165
|
+
return text;
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
export { locales, slugen };
|
|
169
|
+
//# sourceMappingURL=index.mjs.map
|
|
170
|
+
//# sourceMappingURL=index.mjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/locales.ts","../src/symbols.ts","../src/slugen.ts"],"names":["escapeRegex"],"mappings":";AAKO,IAAM,OAAA,GAAqC;AAAA,EAChD,EAAA,EAAI;AAAA,IACF,MAAA,EAAG,GAAA;AAAA,IACH,MAAA,EAAG,GAAA;AAAA,IACH,MAAA,EAAG,GAAA;AAAA,IACH,MAAA,EAAG,GAAA;AAAA,IACH,MAAA,EAAG,GAAA;AAAA,IACH,MAAA,EAAG,GAAA;AAAA,IACH,MAAA,EAAG;AAAA,GACL;AAAA,EAEA,EAAA,EAAI;AAAA,IACF,MAAA,EAAG,GAAA;AAAA,IACH,MAAA,EAAG,GAAA;AAAA,IACH,MAAA,EAAG,GAAA;AAAA,IACH,MAAA,EAAG,GAAA;AAAA,IACH,MAAA,EAAG,GAAA;AAAA,IACH,MAAA,EAAG,GAAA;AAAA,IACH,MAAA,EAAG,GAAA;AAAA,IACH,MAAA,EAAG,GAAA;AAAA,IACH,MAAA,EAAG,GAAA;AAAA,IACH,MAAA,EAAG,GAAA;AAAA,IACH,MAAA,EAAG,GAAA;AAAA,IACH,MAAA,EAAG,GAAA;AAAA,IACH,MAAA,EAAG,GAAA;AAAA,IACH,MAAA,EAAG,GAAA;AAAA,IACH,MAAA,EAAG,GAAA;AAAA,IACH,MAAA,EAAG,GAAA;AAAA,IACH,MAAA,EAAG,GAAA;AAAA,IACH,MAAA,EAAG,GAAA;AAAA,IACH,MAAA,EAAG,GAAA;AAAA,IACH,MAAA,EAAG,GAAA;AAAA,IACH,MAAA,EAAG,GAAA;AAAA,IACH,MAAA,EAAG,GAAA;AAAA,IACH,MAAA,EAAG,IAAA;AAAA,IACH,MAAA,EAAG;AAAA,GACL;AAAA,EAEA,EAAA,EAAI;AAAA,IACF,MAAA,EAAG,GAAA;AAAA,IACH,MAAA,EAAG;AAAA,GACL;AAAA,EAEA,EAAA,EAAI;AAAA,IACF,MAAA,EAAG,GAAA;AAAA,IACH,CAAA,EAAG,GAAA;AAAA,IACH,MAAA,EAAG,GAAA;AAAA,IACH,MAAA,EAAG,GAAA;AAAA,IACH,MAAA,EAAG,GAAA;AAAA,IACH,MAAA,EAAG,GAAA;AAAA,IACH,MAAA,EAAG,GAAA;AAAA,IACH,MAAA,EAAG,GAAA;AAAA,IACH,MAAA,EAAG,GAAA;AAAA,IACH,MAAA,EAAG,GAAA;AAAA,IACH,MAAA,EAAG,GAAA;AAAA,IACH,MAAA,EAAG,GAAA;AAAA,IACH,MAAA,EAAG;AAAA,GACL;AAAA,EAEA,EAAA,EAAI;AAAA,IACF,MAAA,EAAG,GAAA;AAAA,IACH,MAAA,EAAG,GAAA;AAAA,IACH,MAAA,EAAG,GAAA;AAAA,IACH,MAAA,EAAG,GAAA;AAAA,IACH,MAAA,EAAG,GAAA;AAAA,IACH,MAAA,EAAG;AAAA,GACL;AAAA,EAEA,IAAI;AACN;AAKA,IAAM,WAAA,uBAAkB,GAAA,EAA+C;AAKvE,SAAS,aAAa,UAAA,EAAiC;AACrD,EAAA,MAAM,SAAoB,EAAC;AAE3B,EAAA,KAAA,MAAW,OAAO,UAAA,EAAY;AAC5B,IAAA,MAAM,MAAA,GAAS,QAAQ,GAAG,CAAA;AAC1B,IAAA,IAAI,CAAC,MAAA,EAAQ;AACb,IAAA,MAAA,CAAO,MAAA,CAAO,QAAQ,MAAM,CAAA;AAAA,EAC9B;AAEA,EAAA,OAAO,MAAA;AACT;AASO,SAAS,WAAA,CAAY,MAAc,MAAA,EAAoC;AAC5E,EAAA,IAAI,CAAC,QAAQ,OAAO,IAAA;AAEpB,EAAA,MAAM,OAAO,KAAA,CAAM,OAAA,CAAQ,MAAM,CAAA,GAAI,MAAA,GAAS,CAAC,MAAM,CAAA;AACrD,EAAA,MAAM,QAAA,GAAW,IAAA,CAAK,IAAA,EAAK,CAAE,KAAK,GAAG,CAAA;AAErC,EAAA,IAAI,MAAA,GAAS,WAAA,CAAY,GAAA,CAAI,QAAQ,CAAA;AAErC,EAAA,IAAI,CAAC,MAAA,EAAQ;AACX,IAAA,MAAM,GAAA,GAAM,aAAa,IAAI,CAAA;AAE7B,IAAA,IAAI,MAAA,CAAO,IAAA,CAAK,GAAG,CAAA,CAAE,WAAW,CAAA,EAAG;AACjC,MAAA,WAAA,CAAY,IAAI,QAAA,EAAU,EAAE,GAAA,EAAK,KAAA,EAAO,MAAM,CAAA;AAC9C,MAAA,OAAO,IAAA;AAAA,IACT;AAEA,IAAA,MAAM,OAAA,GAAU,IAAI,MAAA,CAAO,CAAA,CAAA,EAAI,MAAA,CAAO,IAAA,CAAK,GAAG,CAAA,CAAE,IAAA,CAAK,EAAE,CAAC,CAAA,CAAA,CAAA,EAAK,GAAG,CAAA;AAEhE,IAAA,MAAA,GAAS,EAAE,GAAA,EAAK,KAAA,EAAO,OAAA,EAAQ;AAC/B,IAAA,WAAA,CAAY,GAAA,CAAI,UAAU,MAAM,CAAA;AAAA,EAClC;AAEA,EAAA,OAAO,IAAA,CAAK,OAAA,CAAQ,MAAA,CAAO,KAAA,EAAO,CAAC,SAAS,MAAA,CAAQ,GAAA,CAAI,IAAI,CAAA,IAAK,IAAI,CAAA;AACvE;;;ACtHO,IAAM,OAAA,GAAkC;AAAA;AAAA,EAE7C,GAAA,EAAK,OAAA;AAAA,EACL,GAAA,EAAK,MAAA;AAAA,EACL,GAAA,EAAK,MAAA;AAAA,EACL,GAAA,EAAK,WAAA;AAAA,EACL,GAAA,EAAK,QAAA;AAAA,EACL,GAAA,EAAK,UAAA;AAAA;AAAA,EAGL,CAAA,EAAG,UAAA;AAAA,EACH,QAAA,EAAK,QAAA;AAAA,EACL,MAAA,EAAK,SAAA;AAAA,EACL,QAAA,EAAK,QAAA;AAAA,EACL,MAAA,EAAK,OAAA;AAAA,EACL,QAAA,EAAK,SAAA;AAAA;AAAA,EAGL,GAAA,EAAK,GAAA;AAAA,EACL,GAAA,EAAK,GAAA;AAAA,EACL,IAAA,EAAM,GAAA;AAAA,EACN,CAAA,EAAG,GAAA;AAAA;AAAA,EAGH,GAAA,EAAK,EAAA;AAAA,EACL,GAAA,EAAK,EAAA;AAAA,EACL,GAAA,EAAK,EAAA;AAAA,EACL,GAAA,EAAK,EAAA;AAAA,EACL,GAAA,EAAK,EAAA;AAAA,EACL,GAAA,EAAK,EAAA;AAAA,EACL,GAAA,EAAK,EAAA;AAAA,EACL,GAAA,EAAK,EAAA;AAAA,EACL,GAAA,EAAK,EAAA;AAAA,EACL,GAAA,EAAK,EAAA;AAAA,EACL,GAAA,EAAK,EAAA;AAAA,EACL,GAAA,EAAK,EAAA;AAAA,EACL,GAAA,EAAK,EAAA;AAAA,EACL,GAAA,EAAK,EAAA;AAAA,EACL,GAAA,EAAK,EAAA;AAAA,EACL,GAAA,EAAK,EAAA;AAAA,EACL,GAAA,EAAK,EAAA;AAAA,EACL,GAAA,EAAK,EAAA;AAAA,EACL,GAAA,EAAK,EAAA;AAAA,EACL,GAAA,EAAK;AACP,CAAA;AAGA,IAAM,cAAc,CAAC,IAAA,KAAiB,IAAA,CAAK,OAAA,CAAQ,uBAAuB,MAAM,CAAA;AAEhF,IAAM,iBAAA,GAAoB,IAAI,MAAA,CAAO,CAAA,CAAA,EAAI,OAAO,IAAA,CAAK,OAAO,CAAA,CAAE,GAAA,CAAI,WAAW,CAAA,CAAE,IAAA,CAAK,EAAE,CAAC,KAAK,GAAG,CAAA;AAExF,SAAS,YAAA,CAAa,MAAc,MAAA,EAAiC;AAC1E,EAAA,MAAM,MAAM,MAAA,GAAS,EAAE,GAAG,OAAA,EAAS,GAAG,QAAO,GAAI,OAAA;AACjD,EAAA,MAAM,QAAQ,GAAA,KAAQ,OAAA,GAAU,oBAAoB,IAAI,MAAA,CAAO,IAAI,MAAA,CAAO,IAAA,CAAK,GAAG,CAAA,CAAE,IAAI,WAAW,CAAA,CAAE,KAAK,EAAE,CAAC,KAAK,GAAG,CAAA;AAErH,EAAA,OAAO,IAAA,CAAK,QAAQ,KAAA,EAAO,CAAC,SAAS,GAAA,CAAI,IAAI,KAAK,EAAE,CAAA;AACtD;;;AC5DA,SAASA,aAAY,IAAA,EAAc;AACjC,EAAA,OAAO,IAAA,CAAK,OAAA,CAAQ,qBAAA,EAAuB,MAAM,CAAA;AACnD;AAEO,SAAS,MAAA,CAAO,KAAA,EAAe,OAAA,GAAyB,EAAC,EAAW;AACzE,EAAA,IAAI,CAAC,OAAO,OAAO,EAAA;AAEnB,EAAA,MAAM,SAAA,GAAY,QAAQ,SAAA,IAAa,GAAA;AACvC,EAAA,MAAM,aAAA,GAAgBA,aAAY,SAAS,CAAA;AAC3C,EAAA,IAAI,IAAA,GAAO,KAAA;AAGX,EAAA,IAAI,OAAA,CAAQ,YAAY,KAAA,EAAO;AAC7B,IAAA,IAAA,GAAO,YAAA,CAAa,IAAA,EAAM,OAAA,CAAQ,kBAAkB,CAAA;AAAA,EACtD;AAGA,EAAA,IAAA,GAAO,WAAA,CAAY,IAAA,EAAM,OAAA,CAAQ,MAAM,CAAA;AAGvC,EAAA,IAAA,GAAO,KAAK,SAAA,CAAU,KAAK,CAAA,CAAE,OAAA,CAAQ,oBAAoB,EAAE,CAAA;AAG3D,EAAA,IAAA,GAAO,IAAA,CAAK,OAAA,CAAQ,gBAAA,EAAkB,SAAS,CAAA;AAG/C,EAAA,IAAA,GAAO,IAAA,CAAK,OAAA,CAAQ,IAAI,MAAA,CAAO,CAAA,CAAA,EAAI,aAAa,CAAA,EAAA,EAAK,aAAa,CAAA,EAAA,CAAA,EAAM,GAAG,CAAA,EAAG,EAAE,CAAA;AAGhF,EAAA,IAAI,OAAA,CAAQ,cAAc,KAAA,EAAO;AAC/B,IAAA,IAAA,GAAO,KAAK,WAAA,EAAY;AAAA,EAC1B;AAEA,EAAA,OAAO,IAAA;AACT","file":"index.mjs","sourcesContent":["type LocaleMap = Record<string, string>;\r\n\r\n/**\r\n * Locale definitions\r\n */\r\nexport const locales: Record<string, LocaleMap> = {\r\n de: {\r\n Ä: \"a\",\r\n ä: \"a\",\r\n Ö: \"o\",\r\n ö: \"o\",\r\n Ü: \"u\",\r\n ü: \"u\",\r\n ß: \"ss\",\r\n },\r\n\r\n fr: {\r\n À: \"a\",\r\n Á: \"a\",\r\n Â: \"a\",\r\n Ã: \"a\",\r\n Ä: \"a\",\r\n Å: \"a\",\r\n à: \"a\",\r\n á: \"a\",\r\n â: \"a\",\r\n ã: \"a\",\r\n ä: \"a\",\r\n å: \"a\",\r\n Ç: \"c\",\r\n ç: \"c\",\r\n È: \"e\",\r\n É: \"e\",\r\n Ê: \"e\",\r\n Ë: \"e\",\r\n è: \"e\",\r\n é: \"e\",\r\n ê: \"e\",\r\n ë: \"e\",\r\n Œ: \"oe\",\r\n œ: \"oe\",\r\n },\r\n\r\n es: {\r\n Ñ: \"n\",\r\n ñ: \"n\",\r\n },\r\n\r\n tr: {\r\n İ: \"i\",\r\n I: \"i\",\r\n ı: \"i\",\r\n Ş: \"s\",\r\n ş: \"s\",\r\n Ğ: \"g\",\r\n ğ: \"g\",\r\n Ö: \"o\",\r\n ö: \"o\",\r\n Ü: \"u\",\r\n ü: \"u\",\r\n Ç: \"c\",\r\n ç: \"c\",\r\n },\r\n\r\n pl: {\r\n Ł: \"l\",\r\n ł: \"l\",\r\n Ś: \"s\",\r\n ś: \"s\",\r\n Ż: \"z\",\r\n ż: \"z\",\r\n },\r\n\r\n en: {},\r\n};\r\n\r\n/**\r\n * Cache: localeKey -> { map, regex }\r\n */\r\nconst localeCache = new Map<string, { map: LocaleMap; regex: RegExp }>();\r\n\r\n/**\r\n * Merge multiple locales into a single map\r\n */\r\nfunction mergeLocales(localeKeys: string[]): LocaleMap {\r\n const merged: LocaleMap = {};\r\n\r\n for (const key of localeKeys) {\r\n const locale = locales[key];\r\n if (!locale) continue;\r\n Object.assign(merged, locale);\r\n }\r\n\r\n return merged;\r\n}\r\n\r\n/**\r\n * Apply locale replacements\r\n *\r\n * Supports:\r\n * - 'fr'\r\n * - ['fr', 'de']\r\n */\r\nexport function applyLocale(text: string, locale?: string | string[]): string {\r\n if (!locale) return text;\r\n\r\n const keys = Array.isArray(locale) ? locale : [locale];\r\n const cacheKey = keys.sort().join(\"+\");\r\n\r\n let cached = localeCache.get(cacheKey);\r\n\r\n if (!cached) {\r\n const map = mergeLocales(keys);\r\n\r\n if (Object.keys(map).length === 0) {\r\n localeCache.set(cacheKey, { map, regex: /$^/ });\r\n return text;\r\n }\r\n\r\n const pattern = new RegExp(`[${Object.keys(map).join(\"\")}]`, \"g\");\r\n\r\n cached = { map, regex: pattern };\r\n localeCache.set(cacheKey, cached);\r\n }\r\n\r\n return text.replace(cached.regex, (char) => cached!.map[char] ?? char);\r\n}\r\n","/**\r\n * Common symbols and their slug-friendly replacements\r\n * Policy:\r\n * - semantic symbols => \" word \"\r\n * - separators => \" \"\r\n * - noise => \"\"\r\n */\r\n\r\nexport const symbols: Record<string, string> = {\r\n // Logical / textual (semantic)\r\n \"&\": \" and \",\r\n \"|\": \" or \",\r\n \"@\": \" at \",\r\n \"%\": \" percent \",\r\n \"+\": \" plus \",\r\n \"=\": \" equals \",\r\n\r\n // Currency (semantic)\r\n $: \" dollar \",\r\n \"€\": \" euro \",\r\n \"£\": \" pound \",\r\n \"₺\": \" lira \",\r\n \"¥\": \" yen \",\r\n \"₹\": \" rupee \",\r\n\r\n // Separators\r\n \".\": \" \",\r\n \"/\": \" \",\r\n \"\\\\\": \" \",\r\n _: \" \",\r\n\r\n // Noise (strip)\r\n \"<\": \"\",\r\n \">\": \"\",\r\n \"~\": \"\",\r\n \"^\": \"\",\r\n \"?\": \"\",\r\n \"!\": \"\",\r\n \",\": \"\",\r\n \":\": \"\",\r\n \";\": \"\",\r\n \"'\": \"\",\r\n '\"': \"\",\r\n \"`\": \"\",\r\n \"*\": \"\",\r\n \"#\": \"\",\r\n \"(\": \"\",\r\n \")\": \"\",\r\n \"[\": \"\",\r\n \"]\": \"\",\r\n \"{\": \"\",\r\n \"}\": \"\",\r\n};\r\n\r\n/** Escape regex special chars safely */\r\nconst escapeRegex = (char: string) => char.replace(/[.*+?^${}()|[\\]\\\\]/g, \"\\\\$&\");\r\n\r\nconst BASE_SYMBOL_REGEX = new RegExp(`[${Object.keys(symbols).map(escapeRegex).join(\"\")}]`, \"g\");\r\n\r\nexport function applySymbols(text: string, custom?: Record<string, string>) {\r\n const map = custom ? { ...symbols, ...custom } : symbols;\r\n const regex = map === symbols ? BASE_SYMBOL_REGEX : new RegExp(`[${Object.keys(map).map(escapeRegex).join(\"\")}]`, \"g\");\r\n\r\n return text.replace(regex, (char) => map[char] ?? \"\");\r\n}\r\n","import { SlugenOptions } from \"./types\";\r\nimport { applyLocale } from \"./locales\";\r\nimport { applySymbols } from \"./symbols\";\r\n\r\nfunction escapeRegex(char: string) {\r\n return char.replace(/[.*+?^${}()|[\\]\\\\]/g, \"\\\\$&\");\r\n}\r\n\r\nexport function slugen(input: string, options: SlugenOptions = {}): string {\r\n if (!input) return \"\";\r\n\r\n const separator = options.separator ?? \"-\";\r\n const safeSeparator = escapeRegex(separator);\r\n let text = input;\r\n\r\n // Symbols (default true)\r\n if (options.symbols !== false) {\r\n text = applySymbols(text, options.customReplacements);\r\n }\r\n\r\n // Locale\r\n text = applyLocale(text, options.locale);\r\n\r\n // Normalize & remove diacritics\r\n text = text.normalize(\"NFD\").replace(/[\\u0300-\\u036f]/g, \"\");\r\n\r\n // Replace non-alphanumeric with separator\r\n text = text.replace(/[^a-zA-Z0-9]+/g, separator);\r\n\r\n // Trim separator (SAFE)\r\n text = text.replace(new RegExp(`^${safeSeparator}+|${safeSeparator}+$`, \"g\"), \"\");\r\n\r\n // Lowercase (default true)\r\n if (options.lowercase !== false) {\r\n text = text.toLowerCase();\r\n }\r\n\r\n return text;\r\n}\r\n"]}
|
package/package.json
ADDED
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "slugen",
|
|
3
|
+
"version": "0.1.1",
|
|
4
|
+
"description": "Modern and lightweight slug generator for URLs, permalinks, and identifiers with multi-language support",
|
|
5
|
+
"keywords": [
|
|
6
|
+
"slug",
|
|
7
|
+
"slug-generator",
|
|
8
|
+
"url-slug",
|
|
9
|
+
"permalink",
|
|
10
|
+
"seo",
|
|
11
|
+
"url",
|
|
12
|
+
"identifier",
|
|
13
|
+
"string-normalization",
|
|
14
|
+
"multilingual",
|
|
15
|
+
"i18n",
|
|
16
|
+
"typescript"
|
|
17
|
+
],
|
|
18
|
+
"main": "./dist/index.cjs",
|
|
19
|
+
"module": "./dist/index.js",
|
|
20
|
+
"types": "./dist/index.d.ts",
|
|
21
|
+
"exports": {
|
|
22
|
+
".": {
|
|
23
|
+
"types": "./dist/index.d.ts",
|
|
24
|
+
"import": "./dist/index.js",
|
|
25
|
+
"require": "./dist/index.cjs"
|
|
26
|
+
}
|
|
27
|
+
},
|
|
28
|
+
"files": [
|
|
29
|
+
"dist"
|
|
30
|
+
],
|
|
31
|
+
"sideEffects": false,
|
|
32
|
+
"homepage": "https://github.com/kabledev/slugen",
|
|
33
|
+
"repository": {
|
|
34
|
+
"type": "git",
|
|
35
|
+
"url": "git+https://github.com/kabledev/slugen.git"
|
|
36
|
+
},
|
|
37
|
+
"author": "Kable",
|
|
38
|
+
"license": "MIT",
|
|
39
|
+
"scripts": {
|
|
40
|
+
"build": "tsup",
|
|
41
|
+
"dev": "tsup --watch",
|
|
42
|
+
"typecheck": "tsc --noEmit",
|
|
43
|
+
"test": "vitest tests/slugen.spec.ts --run",
|
|
44
|
+
"bench": "vitest tests/bench.spec.ts --run"
|
|
45
|
+
},
|
|
46
|
+
"devDependencies": {
|
|
47
|
+
"tsup": "^8.5.1",
|
|
48
|
+
"typescript": "^5.9.3",
|
|
49
|
+
"vitest": "^4.0.16"
|
|
50
|
+
}
|
|
51
|
+
}
|