trtext 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 +21 -0
- package/README.md +157 -0
- package/dist/index.d.ts +110 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +243 -0
- package/dist/index.js.map +1 -0
- package/package.json +42 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Fikret İmamoğlu
|
|
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,157 @@
|
|
|
1
|
+
# trtext
|
|
2
|
+
|
|
3
|
+
Turkish-correct string operations for JavaScript and TypeScript. Zero
|
|
4
|
+
dependencies, no ICU data required, pure functions only.
|
|
5
|
+
|
|
6
|
+
## The problem
|
|
7
|
+
|
|
8
|
+
`String.prototype.toLowerCase()` is deliberately locale-independent, so it is
|
|
9
|
+
wrong for Turkish:
|
|
10
|
+
|
|
11
|
+
```js
|
|
12
|
+
"I".toLowerCase() // "i" — should be "ı"
|
|
13
|
+
"İ".toLowerCase() // "i̇" — an "i" plus a combining dot above, length 2
|
|
14
|
+
"i".toUpperCase() // "I" — should be "İ"
|
|
15
|
+
```
|
|
16
|
+
|
|
17
|
+
This is the [Turkish locale
|
|
18
|
+
bug](https://mattryall.net/blog/the-infamous-turkish-locale-bug), and it keeps
|
|
19
|
+
resurfacing: it has hit the [Kotlin
|
|
20
|
+
compiler](https://news.ycombinator.com/item?id=45559767),
|
|
21
|
+
[Gradle](https://github.com/gradle/gradle/issues/1506) and
|
|
22
|
+
[Vaadin](https://github.com/vaadin/flow/issues/7705), among many others. The
|
|
23
|
+
usual symptom is an identifier, class name, file extension or user login that
|
|
24
|
+
silently stops matching on a Turkish machine.
|
|
25
|
+
|
|
26
|
+
`toLocaleLowerCase("tr")` fixes the first line above, but it depends on the
|
|
27
|
+
host's ICU build (absent in some minimal Node builds, React Native and older
|
|
28
|
+
embedded runtimes) and it still leaves the combining dot behind on the second
|
|
29
|
+
line. This package is explicit and produces identical results everywhere.
|
|
30
|
+
|
|
31
|
+
## Install
|
|
32
|
+
|
|
33
|
+
```sh
|
|
34
|
+
npm install trtext
|
|
35
|
+
```
|
|
36
|
+
|
|
37
|
+
## Usage
|
|
38
|
+
|
|
39
|
+
```js
|
|
40
|
+
import {
|
|
41
|
+
toLowerCaseTr,
|
|
42
|
+
toUpperCaseTr,
|
|
43
|
+
foldTr,
|
|
44
|
+
slugTr,
|
|
45
|
+
sortTr,
|
|
46
|
+
capitalizeTr,
|
|
47
|
+
} from "trtext"
|
|
48
|
+
|
|
49
|
+
toLowerCaseTr("İZMİR") // "izmir"
|
|
50
|
+
toLowerCaseTr("ISTANBUL") // "ıstanbul"
|
|
51
|
+
toUpperCaseTr("ığdır") // "IĞDIR"
|
|
52
|
+
capitalizeTr("istanbul") // "İstanbul"
|
|
53
|
+
|
|
54
|
+
foldTr(" ÇAĞRI Merkezi ") // "cagri merkezi"
|
|
55
|
+
slugTr("Şanlıurfa'da Güneş") // "sanliurfada-gunes"
|
|
56
|
+
|
|
57
|
+
sortTr(["Üsküdar", "Zonguldak", "Çorum", "Iğdır"])
|
|
58
|
+
// ["Çorum", "Iğdır", "Üsküdar", "Zonguldak"]
|
|
59
|
+
```
|
|
60
|
+
|
|
61
|
+
### Case-insensitive search that actually works
|
|
62
|
+
|
|
63
|
+
The common bug is a user typing `istanbul` and not matching a stored
|
|
64
|
+
`İSTANBUL`. Fold both sides to one comparison key:
|
|
65
|
+
|
|
66
|
+
```js
|
|
67
|
+
const matches = cities.filter((city) => foldTr(city).includes(foldTr(query)))
|
|
68
|
+
```
|
|
69
|
+
|
|
70
|
+
## API
|
|
71
|
+
|
|
72
|
+
| Function | Description |
|
|
73
|
+
| --- | --- |
|
|
74
|
+
| `toLowerCaseTr(s)` | Lowercase with Turkish rules. `I` → `ı`, `İ` → `i`, and `I` + combining dot above → `i`. |
|
|
75
|
+
| `toUpperCaseTr(s)` | Uppercase with Turkish rules. `i` → `İ`, `ı` → `I`. |
|
|
76
|
+
| `capitalizeTr(s)` | First letter up, the rest down. |
|
|
77
|
+
| `titleCaseTr(s)` | Capitalize every word, preserving the original spacing. |
|
|
78
|
+
| `asciifyTr(s)` | Transliterate `çğıöşüâîû` to ASCII, preserving case. |
|
|
79
|
+
| `foldTr(s)` | Turkish lowercase + diacritics stripped + whitespace collapsed. A comparison key for search and deduplication. |
|
|
80
|
+
| `slugTr(s, opts?)` | URL slug. Options: `separator` (default `"-"`), `preserveTurkish` (default `false`). |
|
|
81
|
+
| `compareTr(a, b)` | Comparator for `Array.prototype.sort`, using Turkish alphabet order. |
|
|
82
|
+
| `sortTr(strings)` | New sorted array; the input is not mutated. |
|
|
83
|
+
| `TURKISH_ALPHABET` | The 29 letters in collation order. |
|
|
84
|
+
|
|
85
|
+
Collation follows the Turkish alphabet, where `ç` follows `c`, `ğ` follows
|
|
86
|
+
`g`, `ı` precedes `i`, `ö` follows `o`, `ş` follows `s` and `ü` follows `u`.
|
|
87
|
+
The test suite asserts that `sortTr` produces exactly the same order as
|
|
88
|
+
`Intl.Collator("tr")` on runtimes that ship Turkish collation data, and skips
|
|
89
|
+
rather than fails on runtimes that do not.
|
|
90
|
+
|
|
91
|
+
Characters outside the Turkish alphabet (digits, punctuation, foreign letters)
|
|
92
|
+
sort after it in code point order, so the ordering is always deterministic.
|
|
93
|
+
|
|
94
|
+
## Related work
|
|
95
|
+
|
|
96
|
+
- [`Intl.Collator`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Intl/Collator)
|
|
97
|
+
and `toLocaleLowerCase("tr")` — use these when you can rely on ICU being
|
|
98
|
+
present and you do not mind the combining-dot result.
|
|
99
|
+
- [`slugify`](https://www.npmjs.com/package/slugify) — a general-purpose
|
|
100
|
+
slugger with a much wider character map, but it lowercases with the default
|
|
101
|
+
locale, so dotted and dotless `i` get mangled.
|
|
102
|
+
- [`turkish-deasciifier`](https://www.npmjs.com/package/turkish-deasciifier) —
|
|
103
|
+
solves the opposite problem: guessing the missing diacritics in text typed on
|
|
104
|
+
an ASCII keyboard.
|
|
105
|
+
|
|
106
|
+
## Development
|
|
107
|
+
|
|
108
|
+
```sh
|
|
109
|
+
npm install
|
|
110
|
+
npm test # builds, then runs the suite on the compiled output
|
|
111
|
+
```
|
|
112
|
+
|
|
113
|
+
## License
|
|
114
|
+
|
|
115
|
+
MIT
|
|
116
|
+
|
|
117
|
+
---
|
|
118
|
+
|
|
119
|
+
<details>
|
|
120
|
+
<summary>🇹🇷 Türkçe</summary>
|
|
121
|
+
|
|
122
|
+
### Neden gerekli
|
|
123
|
+
|
|
124
|
+
JavaScript'in `toLowerCase()` fonksiyonu bilinçli olarak yerel ayardan
|
|
125
|
+
bağımsızdır, bu yüzden Türkçede hatalı çalışır: `"I".toLowerCase()` sonucu
|
|
126
|
+
`"ı"` yerine `"i"` verir, `"İ".toLowerCase()` ise temiz bir `"i"` yerine
|
|
127
|
+
üzerinde birleşen nokta taşıyan iki karakterlik bir dize döndürür.
|
|
128
|
+
`toLocaleLowerCase("tr")` ilk sorunu çözer ama çalıştığı ortamdaki ICU verisine
|
|
129
|
+
bağlıdır ve ikinci sorunu çözmez.
|
|
130
|
+
|
|
131
|
+
En sık görülen sonuç: kullanıcı `istanbul` yazıyor, veritabanındaki `İSTANBUL`
|
|
132
|
+
kaydı bulunamıyor. `foldTr` her iki tarafı tek bir karşılaştırma anahtarına
|
|
133
|
+
indirger ve sorun ortadan kalkar.
|
|
134
|
+
|
|
135
|
+
### Kurulum ve kullanım
|
|
136
|
+
|
|
137
|
+
```sh
|
|
138
|
+
npm install trtext
|
|
139
|
+
```
|
|
140
|
+
|
|
141
|
+
```js
|
|
142
|
+
import { toLowerCaseTr, slugTr, sortTr } from "trtext"
|
|
143
|
+
|
|
144
|
+
toLowerCaseTr("İZMİR") // "izmir"
|
|
145
|
+
slugTr("Şanlıurfa'da Güneş") // "sanliurfada-gunes"
|
|
146
|
+
sortTr(["Üsküdar", "Çorum"]) // ["Çorum", "Üsküdar"]
|
|
147
|
+
```
|
|
148
|
+
|
|
149
|
+
Sıralama Türk alfabesi düzenini izler: `ç` harfi `c`'den, `ğ` harfi `g`'den,
|
|
150
|
+
`ö` harfi `o`'dan, `ş` harfi `s`'den ve `ü` harfi `u`'dan sonra gelir; `ı`
|
|
151
|
+
harfi `i`'den önce gelir. Testler, Türkçe sıralama verisi bulunan ortamlarda
|
|
152
|
+
sonucun `Intl.Collator("tr")` ile birebir aynı olduğunu doğrular.
|
|
153
|
+
|
|
154
|
+
Tüm fonksiyonların listesi için yukarıdaki API tablosuna bakın. Katkılar ve
|
|
155
|
+
hata bildirimleri memnuniyetle karşılanır.
|
|
156
|
+
|
|
157
|
+
</details>
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,110 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Turkish-correct text utilities. Zero dependencies.
|
|
3
|
+
*
|
|
4
|
+
* Why this exists: JavaScript's `toLowerCase()` / `toUpperCase()` are
|
|
5
|
+
* locale-independent, so `"I".toLowerCase()` returns `"i"` instead of the
|
|
6
|
+
* Turkish `"ı"`, and `"İ".toLowerCase()` returns `"i\u0307"` (an `i` plus a
|
|
7
|
+
* combining dot above) rather than a clean `"i"`. `toLocaleLowerCase("tr")`
|
|
8
|
+
* fixes the first case but still depends on the host ICU build and still
|
|
9
|
+
* leaves the combining mark behind in the second. These helpers are explicit
|
|
10
|
+
* and deterministic on every runtime.
|
|
11
|
+
*/
|
|
12
|
+
/**
|
|
13
|
+
* Lowercase a string using Turkish rules.
|
|
14
|
+
*
|
|
15
|
+
* - `I` -> `ı`
|
|
16
|
+
* - `İ` (U+0130) -> `i`
|
|
17
|
+
* - `I` followed by a combining dot above -> `i`
|
|
18
|
+
*
|
|
19
|
+
* @example
|
|
20
|
+
* toLowerCaseTr("ISTANBUL") // "ıstanbul"
|
|
21
|
+
* toLowerCaseTr("İZMİR") // "izmir"
|
|
22
|
+
*/
|
|
23
|
+
export declare function toLowerCaseTr(input: string): string;
|
|
24
|
+
/**
|
|
25
|
+
* Uppercase a string using Turkish rules.
|
|
26
|
+
*
|
|
27
|
+
* - `i` -> `İ`
|
|
28
|
+
* - `ı` -> `I`
|
|
29
|
+
*
|
|
30
|
+
* @example
|
|
31
|
+
* toUpperCaseTr("iğne") // "İĞNE"
|
|
32
|
+
*/
|
|
33
|
+
export declare function toUpperCaseTr(input: string): string;
|
|
34
|
+
/**
|
|
35
|
+
* Replace Turkish-specific letters with their closest ASCII equivalents,
|
|
36
|
+
* preserving letter case. Characters outside the Turkish set are untouched.
|
|
37
|
+
*
|
|
38
|
+
* @example
|
|
39
|
+
* asciifyTr("Çiğdem Şahin") // "Cigdem Sahin"
|
|
40
|
+
*/
|
|
41
|
+
export declare function asciifyTr(input: string): string;
|
|
42
|
+
/**
|
|
43
|
+
* Normalize a string for case-insensitive, diacritic-insensitive comparison
|
|
44
|
+
* and search. Applies Turkish lowercasing, then strips Turkish diacritics,
|
|
45
|
+
* then collapses whitespace.
|
|
46
|
+
*
|
|
47
|
+
* Both `"İSTANBUL"` and `"istanbul"` fold to `"istanbul"`, so user input
|
|
48
|
+
* matches stored data regardless of how either was capitalized.
|
|
49
|
+
*
|
|
50
|
+
* @example
|
|
51
|
+
* foldTr(" ÇAĞRI Merkezi ") // "cagri merkezi"
|
|
52
|
+
*/
|
|
53
|
+
export declare function foldTr(input: string): string;
|
|
54
|
+
export type SlugOptions = {
|
|
55
|
+
/** Separator between words. Defaults to `"-"`. */
|
|
56
|
+
separator?: string;
|
|
57
|
+
/**
|
|
58
|
+
* Keep Turkish letters instead of transliterating them to ASCII.
|
|
59
|
+
* Defaults to `false`, which is what URLs almost always want.
|
|
60
|
+
*/
|
|
61
|
+
preserveTurkish?: boolean;
|
|
62
|
+
};
|
|
63
|
+
/**
|
|
64
|
+
* Build a URL-safe slug from Turkish text.
|
|
65
|
+
*
|
|
66
|
+
* Unlike generic slug libraries, this lowercases with Turkish rules first, so
|
|
67
|
+
* dotted and dotless `i` survive the round trip instead of being mangled by
|
|
68
|
+
* the default locale.
|
|
69
|
+
*
|
|
70
|
+
* @example
|
|
71
|
+
* slugTr("Şanlıurfa'da Güneş") // "sanliurfada-gunes"
|
|
72
|
+
*/
|
|
73
|
+
export declare function slugTr(input: string, options?: SlugOptions): string;
|
|
74
|
+
/** The Turkish alphabet in collation order. */
|
|
75
|
+
export declare const TURKISH_ALPHABET: readonly ["a", "b", "c", "ç", "d", "e", "f", "g", "ğ", "h", "ı", "i", "j", "k", "l", "m", "n", "o", "ö", "p", "r", "s", "ş", "t", "u", "ü", "v", "y", "z"];
|
|
76
|
+
/**
|
|
77
|
+
* Compare two strings using Turkish alphabet order, where `ç` follows `c`,
|
|
78
|
+
* `ğ` follows `g`, `ı` precedes `i`, `ö` follows `o`, `ş` follows `s` and `ü`
|
|
79
|
+
* follows `u`.
|
|
80
|
+
*
|
|
81
|
+
* Returns a negative number, zero, or a positive number, so it can be passed
|
|
82
|
+
* straight to `Array.prototype.sort`.
|
|
83
|
+
*
|
|
84
|
+
* @example
|
|
85
|
+
* ["çilek", "cam", "ıspanak", "incir"].sort(compareTr)
|
|
86
|
+
* // ["cam", "çilek", "ıspanak", "incir"]
|
|
87
|
+
*/
|
|
88
|
+
export declare function compareTr(a: string, b: string): number;
|
|
89
|
+
/**
|
|
90
|
+
* Sort an array of strings in Turkish alphabet order. Returns a new array and
|
|
91
|
+
* leaves the input untouched.
|
|
92
|
+
*/
|
|
93
|
+
export declare function sortTr(values: ReadonlyArray<string>): Array<string>;
|
|
94
|
+
/**
|
|
95
|
+
* Uppercase the first letter of a string with Turkish rules and lowercase the
|
|
96
|
+
* rest.
|
|
97
|
+
*
|
|
98
|
+
* @example
|
|
99
|
+
* capitalizeTr("istanbul") // "İstanbul"
|
|
100
|
+
*/
|
|
101
|
+
export declare function capitalizeTr(input: string): string;
|
|
102
|
+
/**
|
|
103
|
+
* Capitalize every whitespace-separated word with Turkish rules, preserving
|
|
104
|
+
* the original spacing.
|
|
105
|
+
*
|
|
106
|
+
* @example
|
|
107
|
+
* titleCaseTr("ışık ılgın") // "Işık Ilgın"
|
|
108
|
+
*/
|
|
109
|
+
export declare function titleCaseTr(input: string): string;
|
|
110
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;GAUG;AAIH;;;;;;;;;;GAUG;AACH,wBAAgB,aAAa,CAAC,KAAK,EAAE,MAAM,GAAG,MAAM,CAqBnD;AAED;;;;;;;;GAQG;AACH,wBAAgB,aAAa,CAAC,KAAK,EAAE,MAAM,GAAG,MAAM,CAenD;AAuBD;;;;;;GAMG;AACH,wBAAgB,SAAS,CAAC,KAAK,EAAE,MAAM,GAAG,MAAM,CAM/C;AAED;;;;;;;;;;GAUG;AACH,wBAAgB,MAAM,CAAC,KAAK,EAAE,MAAM,GAAG,MAAM,CAE5C;AAED,MAAM,MAAM,WAAW,GAAG;IACzB,kDAAkD;IAClD,SAAS,CAAC,EAAE,MAAM,CAAA;IAClB;;;OAGG;IACH,eAAe,CAAC,EAAE,OAAO,CAAA;CACzB,CAAA;AAED;;;;;;;;;GASG;AACH,wBAAgB,MAAM,CAAC,KAAK,EAAE,MAAM,EAAE,OAAO,GAAE,WAAgB,GAAG,MAAM,CAevE;AAED,+CAA+C;AAC/C,eAAO,MAAM,gBAAgB,4JA8BnB,CAAA;AAcV;;;;;;;;;;;GAWG;AACH,wBAAgB,SAAS,CAAC,CAAC,EAAE,MAAM,EAAE,CAAC,EAAE,MAAM,GAAG,MAAM,CAStD;AAED;;;GAGG;AACH,wBAAgB,MAAM,CAAC,MAAM,EAAE,aAAa,CAAC,MAAM,CAAC,GAAG,KAAK,CAAC,MAAM,CAAC,CAEnE;AAED;;;;;;GAMG;AACH,wBAAgB,YAAY,CAAC,KAAK,EAAE,MAAM,GAAG,MAAM,CAKlD;AAED;;;;;;GAMG;AACH,wBAAgB,WAAW,CAAC,KAAK,EAAE,MAAM,GAAG,MAAM,CAEjD"}
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,243 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Turkish-correct text utilities. Zero dependencies.
|
|
3
|
+
*
|
|
4
|
+
* Why this exists: JavaScript's `toLowerCase()` / `toUpperCase()` are
|
|
5
|
+
* locale-independent, so `"I".toLowerCase()` returns `"i"` instead of the
|
|
6
|
+
* Turkish `"ı"`, and `"İ".toLowerCase()` returns `"i\u0307"` (an `i` plus a
|
|
7
|
+
* combining dot above) rather than a clean `"i"`. `toLocaleLowerCase("tr")`
|
|
8
|
+
* fixes the first case but still depends on the host ICU build and still
|
|
9
|
+
* leaves the combining mark behind in the second. These helpers are explicit
|
|
10
|
+
* and deterministic on every runtime.
|
|
11
|
+
*/
|
|
12
|
+
const COMBINING_DOT_ABOVE = "\u0307";
|
|
13
|
+
/**
|
|
14
|
+
* Lowercase a string using Turkish rules.
|
|
15
|
+
*
|
|
16
|
+
* - `I` -> `ı`
|
|
17
|
+
* - `İ` (U+0130) -> `i`
|
|
18
|
+
* - `I` followed by a combining dot above -> `i`
|
|
19
|
+
*
|
|
20
|
+
* @example
|
|
21
|
+
* toLowerCaseTr("ISTANBUL") // "ıstanbul"
|
|
22
|
+
* toLowerCaseTr("İZMİR") // "izmir"
|
|
23
|
+
*/
|
|
24
|
+
export function toLowerCaseTr(input) {
|
|
25
|
+
if (input.length === 0)
|
|
26
|
+
return input;
|
|
27
|
+
let out = "";
|
|
28
|
+
for (let i = 0; i < input.length; i++) {
|
|
29
|
+
const ch = input[i];
|
|
30
|
+
if (ch === "I") {
|
|
31
|
+
if (input[i + 1] === COMBINING_DOT_ABOVE) {
|
|
32
|
+
out += "i";
|
|
33
|
+
i++;
|
|
34
|
+
}
|
|
35
|
+
else {
|
|
36
|
+
out += "ı";
|
|
37
|
+
}
|
|
38
|
+
continue;
|
|
39
|
+
}
|
|
40
|
+
if (ch === "\u0130") {
|
|
41
|
+
out += "i";
|
|
42
|
+
continue;
|
|
43
|
+
}
|
|
44
|
+
out += ch.toLowerCase();
|
|
45
|
+
}
|
|
46
|
+
return out;
|
|
47
|
+
}
|
|
48
|
+
/**
|
|
49
|
+
* Uppercase a string using Turkish rules.
|
|
50
|
+
*
|
|
51
|
+
* - `i` -> `İ`
|
|
52
|
+
* - `ı` -> `I`
|
|
53
|
+
*
|
|
54
|
+
* @example
|
|
55
|
+
* toUpperCaseTr("iğne") // "İĞNE"
|
|
56
|
+
*/
|
|
57
|
+
export function toUpperCaseTr(input) {
|
|
58
|
+
if (input.length === 0)
|
|
59
|
+
return input;
|
|
60
|
+
let out = "";
|
|
61
|
+
for (const ch of input) {
|
|
62
|
+
if (ch === "i") {
|
|
63
|
+
out += "\u0130";
|
|
64
|
+
continue;
|
|
65
|
+
}
|
|
66
|
+
if (ch === "ı") {
|
|
67
|
+
out += "I";
|
|
68
|
+
continue;
|
|
69
|
+
}
|
|
70
|
+
out += ch.toUpperCase();
|
|
71
|
+
}
|
|
72
|
+
return out;
|
|
73
|
+
}
|
|
74
|
+
const ASCII_MAP = {
|
|
75
|
+
ç: "c",
|
|
76
|
+
ğ: "g",
|
|
77
|
+
ı: "i",
|
|
78
|
+
ö: "o",
|
|
79
|
+
ş: "s",
|
|
80
|
+
ü: "u",
|
|
81
|
+
"â": "a",
|
|
82
|
+
"î": "i",
|
|
83
|
+
"û": "u",
|
|
84
|
+
Ç: "C",
|
|
85
|
+
Ğ: "G",
|
|
86
|
+
Ö: "O",
|
|
87
|
+
Ş: "S",
|
|
88
|
+
Ü: "U",
|
|
89
|
+
"\u0130": "I",
|
|
90
|
+
"Â": "A",
|
|
91
|
+
"Î": "I",
|
|
92
|
+
"Û": "U",
|
|
93
|
+
};
|
|
94
|
+
/**
|
|
95
|
+
* Replace Turkish-specific letters with their closest ASCII equivalents,
|
|
96
|
+
* preserving letter case. Characters outside the Turkish set are untouched.
|
|
97
|
+
*
|
|
98
|
+
* @example
|
|
99
|
+
* asciifyTr("Çiğdem Şahin") // "Cigdem Sahin"
|
|
100
|
+
*/
|
|
101
|
+
export function asciifyTr(input) {
|
|
102
|
+
let out = "";
|
|
103
|
+
for (const ch of input) {
|
|
104
|
+
out += ASCII_MAP[ch] ?? ch;
|
|
105
|
+
}
|
|
106
|
+
return out;
|
|
107
|
+
}
|
|
108
|
+
/**
|
|
109
|
+
* Normalize a string for case-insensitive, diacritic-insensitive comparison
|
|
110
|
+
* and search. Applies Turkish lowercasing, then strips Turkish diacritics,
|
|
111
|
+
* then collapses whitespace.
|
|
112
|
+
*
|
|
113
|
+
* Both `"İSTANBUL"` and `"istanbul"` fold to `"istanbul"`, so user input
|
|
114
|
+
* matches stored data regardless of how either was capitalized.
|
|
115
|
+
*
|
|
116
|
+
* @example
|
|
117
|
+
* foldTr(" ÇAĞRI Merkezi ") // "cagri merkezi"
|
|
118
|
+
*/
|
|
119
|
+
export function foldTr(input) {
|
|
120
|
+
return asciifyTr(toLowerCaseTr(input)).replace(/\s+/g, " ").trim();
|
|
121
|
+
}
|
|
122
|
+
/**
|
|
123
|
+
* Build a URL-safe slug from Turkish text.
|
|
124
|
+
*
|
|
125
|
+
* Unlike generic slug libraries, this lowercases with Turkish rules first, so
|
|
126
|
+
* dotted and dotless `i` survive the round trip instead of being mangled by
|
|
127
|
+
* the default locale.
|
|
128
|
+
*
|
|
129
|
+
* @example
|
|
130
|
+
* slugTr("Şanlıurfa'da Güneş") // "sanliurfada-gunes"
|
|
131
|
+
*/
|
|
132
|
+
export function slugTr(input, options = {}) {
|
|
133
|
+
const separator = options.separator ?? "-";
|
|
134
|
+
const lowered = toLowerCaseTr(input);
|
|
135
|
+
const base = options.preserveTurkish ? lowered : asciifyTr(lowered);
|
|
136
|
+
const allowed = options.preserveTurkish
|
|
137
|
+
? /[^a-z0-9çğıöşü]+/g
|
|
138
|
+
: /[^a-z0-9]+/g;
|
|
139
|
+
const slug = base
|
|
140
|
+
.normalize("NFC")
|
|
141
|
+
.replace(/['’`]/g, "")
|
|
142
|
+
.replace(allowed, separator);
|
|
143
|
+
const escaped = separator.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
144
|
+
return slug
|
|
145
|
+
.replace(new RegExp(`${escaped}{2,}`, "g"), separator)
|
|
146
|
+
.replace(new RegExp(`^${escaped}|${escaped}$`, "g"), "");
|
|
147
|
+
}
|
|
148
|
+
/** The Turkish alphabet in collation order. */
|
|
149
|
+
export const TURKISH_ALPHABET = [
|
|
150
|
+
"a",
|
|
151
|
+
"b",
|
|
152
|
+
"c",
|
|
153
|
+
"ç",
|
|
154
|
+
"d",
|
|
155
|
+
"e",
|
|
156
|
+
"f",
|
|
157
|
+
"g",
|
|
158
|
+
"ğ",
|
|
159
|
+
"h",
|
|
160
|
+
"ı",
|
|
161
|
+
"i",
|
|
162
|
+
"j",
|
|
163
|
+
"k",
|
|
164
|
+
"l",
|
|
165
|
+
"m",
|
|
166
|
+
"n",
|
|
167
|
+
"o",
|
|
168
|
+
"ö",
|
|
169
|
+
"p",
|
|
170
|
+
"r",
|
|
171
|
+
"s",
|
|
172
|
+
"ş",
|
|
173
|
+
"t",
|
|
174
|
+
"u",
|
|
175
|
+
"ü",
|
|
176
|
+
"v",
|
|
177
|
+
"y",
|
|
178
|
+
"z",
|
|
179
|
+
];
|
|
180
|
+
const COLLATION_RANK = new Map(TURKISH_ALPHABET.map((letter, index) => [letter, index]));
|
|
181
|
+
function rankOf(ch) {
|
|
182
|
+
const rank = COLLATION_RANK.get(ch);
|
|
183
|
+
if (rank !== undefined)
|
|
184
|
+
return rank;
|
|
185
|
+
// Unknown characters (digits, punctuation, foreign letters) sort after the
|
|
186
|
+
// Turkish alphabet, in code point order, so ordering stays deterministic.
|
|
187
|
+
return TURKISH_ALPHABET.length + (ch.codePointAt(0) ?? 0);
|
|
188
|
+
}
|
|
189
|
+
/**
|
|
190
|
+
* Compare two strings using Turkish alphabet order, where `ç` follows `c`,
|
|
191
|
+
* `ğ` follows `g`, `ı` precedes `i`, `ö` follows `o`, `ş` follows `s` and `ü`
|
|
192
|
+
* follows `u`.
|
|
193
|
+
*
|
|
194
|
+
* Returns a negative number, zero, or a positive number, so it can be passed
|
|
195
|
+
* straight to `Array.prototype.sort`.
|
|
196
|
+
*
|
|
197
|
+
* @example
|
|
198
|
+
* ["çilek", "cam", "ıspanak", "incir"].sort(compareTr)
|
|
199
|
+
* // ["cam", "çilek", "ıspanak", "incir"]
|
|
200
|
+
*/
|
|
201
|
+
export function compareTr(a, b) {
|
|
202
|
+
const left = [...toLowerCaseTr(a.normalize("NFC"))];
|
|
203
|
+
const right = [...toLowerCaseTr(b.normalize("NFC"))];
|
|
204
|
+
const length = Math.min(left.length, right.length);
|
|
205
|
+
for (let i = 0; i < length; i++) {
|
|
206
|
+
const diff = rankOf(left[i]) - rankOf(right[i]);
|
|
207
|
+
if (diff !== 0)
|
|
208
|
+
return diff;
|
|
209
|
+
}
|
|
210
|
+
return left.length - right.length;
|
|
211
|
+
}
|
|
212
|
+
/**
|
|
213
|
+
* Sort an array of strings in Turkish alphabet order. Returns a new array and
|
|
214
|
+
* leaves the input untouched.
|
|
215
|
+
*/
|
|
216
|
+
export function sortTr(values) {
|
|
217
|
+
return [...values].sort(compareTr);
|
|
218
|
+
}
|
|
219
|
+
/**
|
|
220
|
+
* Uppercase the first letter of a string with Turkish rules and lowercase the
|
|
221
|
+
* rest.
|
|
222
|
+
*
|
|
223
|
+
* @example
|
|
224
|
+
* capitalizeTr("istanbul") // "İstanbul"
|
|
225
|
+
*/
|
|
226
|
+
export function capitalizeTr(input) {
|
|
227
|
+
if (input.length === 0)
|
|
228
|
+
return input;
|
|
229
|
+
const chars = [...input];
|
|
230
|
+
const first = chars[0];
|
|
231
|
+
return toUpperCaseTr(first) + toLowerCaseTr(chars.slice(1).join(""));
|
|
232
|
+
}
|
|
233
|
+
/**
|
|
234
|
+
* Capitalize every whitespace-separated word with Turkish rules, preserving
|
|
235
|
+
* the original spacing.
|
|
236
|
+
*
|
|
237
|
+
* @example
|
|
238
|
+
* titleCaseTr("ışık ılgın") // "Işık Ilgın"
|
|
239
|
+
*/
|
|
240
|
+
export function titleCaseTr(input) {
|
|
241
|
+
return input.replace(/\S+/g, (word) => capitalizeTr(word));
|
|
242
|
+
}
|
|
243
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;GAUG;AAEH,MAAM,mBAAmB,GAAG,QAAQ,CAAA;AAEpC;;;;;;;;;;GAUG;AACH,MAAM,UAAU,aAAa,CAAC,KAAa;IAC1C,IAAI,KAAK,CAAC,MAAM,KAAK,CAAC;QAAE,OAAO,KAAK,CAAA;IACpC,IAAI,GAAG,GAAG,EAAE,CAAA;IACZ,KAAK,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC,GAAG,KAAK,CAAC,MAAM,EAAE,CAAC,EAAE,EAAE,CAAC;QACvC,MAAM,EAAE,GAAG,KAAK,CAAC,CAAC,CAAW,CAAA;QAC7B,IAAI,EAAE,KAAK,GAAG,EAAE,CAAC;YAChB,IAAI,KAAK,CAAC,CAAC,GAAG,CAAC,CAAC,KAAK,mBAAmB,EAAE,CAAC;gBAC1C,GAAG,IAAI,GAAG,CAAA;gBACV,CAAC,EAAE,CAAA;YACJ,CAAC;iBAAM,CAAC;gBACP,GAAG,IAAI,GAAG,CAAA;YACX,CAAC;YACD,SAAQ;QACT,CAAC;QACD,IAAI,EAAE,KAAK,QAAQ,EAAE,CAAC;YACrB,GAAG,IAAI,GAAG,CAAA;YACV,SAAQ;QACT,CAAC;QACD,GAAG,IAAI,EAAE,CAAC,WAAW,EAAE,CAAA;IACxB,CAAC;IACD,OAAO,GAAG,CAAA;AACX,CAAC;AAED;;;;;;;;GAQG;AACH,MAAM,UAAU,aAAa,CAAC,KAAa;IAC1C,IAAI,KAAK,CAAC,MAAM,KAAK,CAAC;QAAE,OAAO,KAAK,CAAA;IACpC,IAAI,GAAG,GAAG,EAAE,CAAA;IACZ,KAAK,MAAM,EAAE,IAAI,KAAK,EAAE,CAAC;QACxB,IAAI,EAAE,KAAK,GAAG,EAAE,CAAC;YAChB,GAAG,IAAI,QAAQ,CAAA;YACf,SAAQ;QACT,CAAC;QACD,IAAI,EAAE,KAAK,GAAG,EAAE,CAAC;YAChB,GAAG,IAAI,GAAG,CAAA;YACV,SAAQ;QACT,CAAC;QACD,GAAG,IAAI,EAAE,CAAC,WAAW,EAAE,CAAA;IACxB,CAAC;IACD,OAAO,GAAG,CAAA;AACX,CAAC;AAED,MAAM,SAAS,GAA2B;IACzC,CAAC,EAAE,GAAG;IACN,CAAC,EAAE,GAAG;IACN,CAAC,EAAE,GAAG;IACN,CAAC,EAAE,GAAG;IACN,CAAC,EAAE,GAAG;IACN,CAAC,EAAE,GAAG;IACN,GAAG,EAAE,GAAG;IACR,GAAG,EAAE,GAAG;IACR,GAAG,EAAE,GAAG;IACR,CAAC,EAAE,GAAG;IACN,CAAC,EAAE,GAAG;IACN,CAAC,EAAE,GAAG;IACN,CAAC,EAAE,GAAG;IACN,CAAC,EAAE,GAAG;IACN,QAAQ,EAAE,GAAG;IACb,GAAG,EAAE,GAAG;IACR,GAAG,EAAE,GAAG;IACR,GAAG,EAAE,GAAG;CACR,CAAA;AAED;;;;;;GAMG;AACH,MAAM,UAAU,SAAS,CAAC,KAAa;IACtC,IAAI,GAAG,GAAG,EAAE,CAAA;IACZ,KAAK,MAAM,EAAE,IAAI,KAAK,EAAE,CAAC;QACxB,GAAG,IAAI,SAAS,CAAC,EAAE,CAAC,IAAI,EAAE,CAAA;IAC3B,CAAC;IACD,OAAO,GAAG,CAAA;AACX,CAAC;AAED;;;;;;;;;;GAUG;AACH,MAAM,UAAU,MAAM,CAAC,KAAa;IACnC,OAAO,SAAS,CAAC,aAAa,CAAC,KAAK,CAAC,CAAC,CAAC,OAAO,CAAC,MAAM,EAAE,GAAG,CAAC,CAAC,IAAI,EAAE,CAAA;AACnE,CAAC;AAYD;;;;;;;;;GASG;AACH,MAAM,UAAU,MAAM,CAAC,KAAa,EAAE,UAAuB,EAAE;IAC9D,MAAM,SAAS,GAAG,OAAO,CAAC,SAAS,IAAI,GAAG,CAAA;IAC1C,MAAM,OAAO,GAAG,aAAa,CAAC,KAAK,CAAC,CAAA;IACpC,MAAM,IAAI,GAAG,OAAO,CAAC,eAAe,CAAC,CAAC,CAAC,OAAO,CAAC,CAAC,CAAC,SAAS,CAAC,OAAO,CAAC,CAAA;IACnE,MAAM,OAAO,GAAG,OAAO,CAAC,eAAe;QACtC,CAAC,CAAC,mBAAmB;QACrB,CAAC,CAAC,aAAa,CAAA;IAChB,MAAM,IAAI,GAAG,IAAI;SACf,SAAS,CAAC,KAAK,CAAC;SAChB,OAAO,CAAC,QAAQ,EAAE,EAAE,CAAC;SACrB,OAAO,CAAC,OAAO,EAAE,SAAS,CAAC,CAAA;IAC7B,MAAM,OAAO,GAAG,SAAS,CAAC,OAAO,CAAC,qBAAqB,EAAE,MAAM,CAAC,CAAA;IAChE,OAAO,IAAI;SACT,OAAO,CAAC,IAAI,MAAM,CAAC,GAAG,OAAO,MAAM,EAAE,GAAG,CAAC,EAAE,SAAS,CAAC;SACrD,OAAO,CAAC,IAAI,MAAM,CAAC,IAAI,OAAO,IAAI,OAAO,GAAG,EAAE,GAAG,CAAC,EAAE,EAAE,CAAC,CAAA;AAC1D,CAAC;AAED,+CAA+C;AAC/C,MAAM,CAAC,MAAM,gBAAgB,GAAG;IAC/B,GAAG;IACH,GAAG;IACH,GAAG;IACH,GAAG;IACH,GAAG;IACH,GAAG;IACH,GAAG;IACH,GAAG;IACH,GAAG;IACH,GAAG;IACH,GAAG;IACH,GAAG;IACH,GAAG;IACH,GAAG;IACH,GAAG;IACH,GAAG;IACH,GAAG;IACH,GAAG;IACH,GAAG;IACH,GAAG;IACH,GAAG;IACH,GAAG;IACH,GAAG;IACH,GAAG;IACH,GAAG;IACH,GAAG;IACH,GAAG;IACH,GAAG;IACH,GAAG;CACM,CAAA;AAEV,MAAM,cAAc,GAAG,IAAI,GAAG,CAC7B,gBAAgB,CAAC,GAAG,CAAC,CAAC,MAAM,EAAE,KAAK,EAAE,EAAE,CAAC,CAAC,MAAM,EAAE,KAAK,CAAC,CAAC,CACxD,CAAA;AAED,SAAS,MAAM,CAAC,EAAU;IACzB,MAAM,IAAI,GAAG,cAAc,CAAC,GAAG,CAAC,EAAE,CAAC,CAAA;IACnC,IAAI,IAAI,KAAK,SAAS;QAAE,OAAO,IAAI,CAAA;IACnC,2EAA2E;IAC3E,0EAA0E;IAC1E,OAAO,gBAAgB,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC,WAAW,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAA;AAC1D,CAAC;AAED;;;;;;;;;;;GAWG;AACH,MAAM,UAAU,SAAS,CAAC,CAAS,EAAE,CAAS;IAC7C,MAAM,IAAI,GAAG,CAAC,GAAG,aAAa,CAAC,CAAC,CAAC,SAAS,CAAC,KAAK,CAAC,CAAC,CAAC,CAAA;IACnD,MAAM,KAAK,GAAG,CAAC,GAAG,aAAa,CAAC,CAAC,CAAC,SAAS,CAAC,KAAK,CAAC,CAAC,CAAC,CAAA;IACpD,MAAM,MAAM,GAAG,IAAI,CAAC,GAAG,CAAC,IAAI,CAAC,MAAM,EAAE,KAAK,CAAC,MAAM,CAAC,CAAA;IAClD,KAAK,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC,GAAG,MAAM,EAAE,CAAC,EAAE,EAAE,CAAC;QACjC,MAAM,IAAI,GAAG,MAAM,CAAC,IAAI,CAAC,CAAC,CAAW,CAAC,GAAG,MAAM,CAAC,KAAK,CAAC,CAAC,CAAW,CAAC,CAAA;QACnE,IAAI,IAAI,KAAK,CAAC;YAAE,OAAO,IAAI,CAAA;IAC5B,CAAC;IACD,OAAO,IAAI,CAAC,MAAM,GAAG,KAAK,CAAC,MAAM,CAAA;AAClC,CAAC;AAED;;;GAGG;AACH,MAAM,UAAU,MAAM,CAAC,MAA6B;IACnD,OAAO,CAAC,GAAG,MAAM,CAAC,CAAC,IAAI,CAAC,SAAS,CAAC,CAAA;AACnC,CAAC;AAED;;;;;;GAMG;AACH,MAAM,UAAU,YAAY,CAAC,KAAa;IACzC,IAAI,KAAK,CAAC,MAAM,KAAK,CAAC;QAAE,OAAO,KAAK,CAAA;IACpC,MAAM,KAAK,GAAG,CAAC,GAAG,KAAK,CAAC,CAAA;IACxB,MAAM,KAAK,GAAG,KAAK,CAAC,CAAC,CAAW,CAAA;IAChC,OAAO,aAAa,CAAC,KAAK,CAAC,GAAG,aAAa,CAAC,KAAK,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,EAAE,CAAC,CAAC,CAAA;AACrE,CAAC;AAED;;;;;;GAMG;AACH,MAAM,UAAU,WAAW,CAAC,KAAa;IACxC,OAAO,KAAK,CAAC,OAAO,CAAC,MAAM,EAAE,CAAC,IAAI,EAAE,EAAE,CAAC,YAAY,CAAC,IAAI,CAAC,CAAC,CAAA;AAC3D,CAAC"}
|
package/package.json
ADDED
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "trtext",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"description": "Turkish-correct lowercase, uppercase, folding, slugs and alphabet sorting. Zero dependencies, no ICU required.",
|
|
5
|
+
"keywords": [
|
|
6
|
+
"turkish",
|
|
7
|
+
"turkce",
|
|
8
|
+
"locale",
|
|
9
|
+
"tolowercase",
|
|
10
|
+
"touppercase",
|
|
11
|
+
"dotless-i",
|
|
12
|
+
"slug",
|
|
13
|
+
"slugify",
|
|
14
|
+
"collation",
|
|
15
|
+
"normalize",
|
|
16
|
+
"diacritics",
|
|
17
|
+
"i18n"
|
|
18
|
+
],
|
|
19
|
+
"license": "MIT",
|
|
20
|
+
"type": "module",
|
|
21
|
+
"exports": {
|
|
22
|
+
".": {
|
|
23
|
+
"types": "./dist/index.d.ts",
|
|
24
|
+
"default": "./dist/index.js"
|
|
25
|
+
}
|
|
26
|
+
},
|
|
27
|
+
"main": "./dist/index.js",
|
|
28
|
+
"types": "./dist/index.d.ts",
|
|
29
|
+
"files": ["dist", "README.md", "LICENSE"],
|
|
30
|
+
"sideEffects": false,
|
|
31
|
+
"engines": {
|
|
32
|
+
"node": ">=18"
|
|
33
|
+
},
|
|
34
|
+
"scripts": {
|
|
35
|
+
"build": "tsc -p tsconfig.json",
|
|
36
|
+
"test": "npm run build && node --test test/*.test.mjs",
|
|
37
|
+
"prepublishOnly": "npm run test"
|
|
38
|
+
},
|
|
39
|
+
"devDependencies": {
|
|
40
|
+
"typescript": "^5.6.0"
|
|
41
|
+
}
|
|
42
|
+
}
|