# Slugs
Source: https://pinyinjs.dev/slug/
Index of every page: https://pinyinjs.dev/llms.txt
`slug` converts Chinese text to a URL-safe string.
```ts
import { slug } from "@kensio/pinyinjs";
slug(dictionary, "我想学中文。"); // "wo3-xiang3-xue2-zhong1wen2"
```
Slugs use dictionary readings and word boundaries:
| Text | This | A slugifier over the characters |
| ---- | ------------ | ------------------------------- |
| 银行 | `yin2hang2` | `yin2xing2` — wrong reading |
| 西安 | `xi1an1` | `xian` — a different word |
| 中文 | `zhong1wen2` | `zhong1-wen2` — not one word |
The conversion preserves these distinctions:
- Context selects the reading. 行 is `háng` in 银行. See
[converting](https://pinyinjs.dev/converting/).
- Separators follow word boundaries. 北京市银行 becomes
`bei3jing1-shi4-yin2hang2`. See [orthography](https://pinyinjs.dev/orthography/).
- Syllable boundaries remain distinct. A toneless slug replaces the apostrophe
in `xī'ān` with a separator, producing `xi-an`.
## Tones
```ts
slug(dictionary, "重庆火锅"); // "chong2qing4-huo3guo1"
slug(dictionary, "重庆火锅", { tones: "none" }); // "chongqing-huoguo"
```
Tone numbers are included by default to distinguish readings. Without tones,
树, 书 and 输 all become `shu`.
Different words with the same pronunciation still produce the same slug:
```ts
slug(dictionary, "权利"); // "quan2li4"
slug(dictionary, "权力"); // "quan2li4"
```
Use a hash suffix to reduce these collisions.
## Hash suffixes
`hash` appends a short hash of the original text.
```ts
slug(dictionary, "权利", { hash: true }); // "quan2li4-1lpt"
slug(dictionary, "权力", { hash: true }); // "quan2li4-uta0"
```
The suffix is calculated from the source text. Homophones can therefore get
different suffixes, and a reading change in a later release leaves the suffix
unchanged.
The default suffix has four base-36 characters (about 1.7 million values).
Use `hash: 6` for six characters. The maximum is seven because the hash is
32 bits.
When enabled, the hash is appended to every slug.
## Uniqueness and stable URLs
Tones and hashes reduce collisions but cannot guarantee uniqueness. Check a new
slug against the slugs already stored by your application.
Store generated slugs as permanent values. Dictionary and reading-rule updates
can change pronunciations or word boundaries in later releases.
## Latin, digits and punctuation
Latin text is folded to ASCII letters, and digits stay as digits. Punctuation,
emoji and other marks become word boundaries.
```ts
slug(dictionary, "iPhone 15 发布"); // "iphone-15-fa1bu4"
slug(dictionary, "《中文》:真好!"); // "zhong1wen2-zhen1-hao3"
slug(dictionary, "2024年报告"); // "2024-nian2-bao4gao4"
```
Set `numbers: "read"` to convert digits to their spoken reading:
```ts
slug(dictionary, "2024年报告", { numbers: "read" });
// "er4-ling2-er4-si4-nian2-bao4gao4"
```
Input with no usable content, such as an empty string or 《》!, returns an
empty string. Set `fallback` to use another value.
```ts
slug(dictionary, "!?。", { fallback: "untitled" }); // "untitled"
```
## Options
| Option | Default | Does |
| ----------- | ------------ | ------------------------------------------- |
| `tones` | `"numbers"` | `"none"` leaves the tones off |
| `separator` | `"-"` | what goes between words |
| `syllables` | `"join"` | `"separate"` cuts every syllable apart |
| `umlaut` | `"v"` | how ü is written; `"u"` merges 绿 into 路 |
| `numbers` | `"keep"` | `"read"` says the digits out |
| `hash` | none | `true` for four characters, or a length |
| `maxLength` | none | the longest it may be, cut at a word |
| `fallback` | `""` | what to write where a text slugs to nothing |
| `locale` | `"zh-CN"` | `"zh-TW"` for 國語 readings |
| `sandhi` | as `convert` | 一, 不 and optional third-tone sandhi |
Slugs control their own formatting. The conversion options `notation`,
`capitals`, `apostrophe`, `punctuation` and `grouping` are unavailable.
`maxLength` truncates at a word boundary and preserves the hash suffix:
```ts
slug(dictionary, "北京市银行", { maxLength: 20 }); // "bei3jing1-shi4"
```
## Search keys and identifiers
Choose a different separator to make identifiers or search keys:
| Want | Options | 中文 |
| -------------- | ---------------------------------- | ------------ |
| A URL slug | none | `zhong1wen2` |
| A search key | `{ tones: "none", separator: "" }` | `zhongwen` |
| A name in code | `{ separator: "_" }` | `zhong1wen2` |
| An anchor id | `{ tones: "none", hash: 4 }` | `zhongwen-…` |
A search key with no tones or separators lets `zhongwen` match 中文 and `xian`
match 西安. This intentionally combines readings that URL slugs distinguish.
```ts
slug(dictionary, "中文", { tones: "none", separator: "" }); // "zhongwen"
```
## At the command line
```console
$ pinyinjs slug 我想学中文。
wo3-xiang3-xue2-zhong1wen2
$ pinyinjs slug --tones none 西安交通大学
xi-an-jiaotong-daxue
$ pinyinjs slug --hash 权利 权力
quan2li4-1lpt
quan2li4-uta0
```
The CLI accepts `--tones`, `--separator`, `--syllables`, `--umlaut`, `--hash`,
`--hash-length`, `--max-length`, `--fallback`, `--read-numbers`, `--locale`,
`--third-tone` and `--no-sandhi`. `--hash-length` enables hashing. With no
arguments, the command reads one title per line from standard input:
```console
$ cat titles.txt | pinyinjs slug --hash
```