Multi-language slug generator supporting 23+ languages including CJK, Arabic, Cyrillic, and more. Faithful Python port of the C# Slugify.MultiLang library.
Project description
slugify-multilang (Python)
A faithful, dependency-free Python port of the C#
Slugify.MultiLanglibrary — a multi-language, URL-safe slug generator with extended locale support and sensible handling of CJK, Arabic, and other non-Latin scripts.
Targets Python 3.10+. Standard library only (re + unicodedata).
Install
pip install slugify-multilang
Or from this folder:
cd python
pip install .
Usage
from slugify_multilang import slugify, SlugifySlugOptions, extend
slugify("Director Fu: Your alt account got exposed again!")
# -> "director-fu-your-alt-account-got-exposed-again"
slugify("Café au lait & cròissant")
# -> "cafe-au-lait-and-croissant"
slugify("傅总:你的马甲 又又又掉了!")
# -> "傅总-你的马甲-又又又掉了"
Replacement-only overload
Mirrors the C# string Slugify(this string, string replacement = "-") overload —
pass a string instead of an options object:
slugify("hello world", "_") # -> "hello_world"
Options
SlugifySlugOptions maps 1:1 to the C# SlugifySlugOptions (PascalCase → snake_case):
| Field | Type | Default | Meaning |
|---|---|---|---|
replacement |
str |
"-" |
Word-joining character. |
remove |
re.Pattern | None |
None |
Per-character strip regex (default built-in when None). |
lower |
bool |
True |
Lowercase the result. |
strict |
bool |
True |
Strip anything that isn't a letter, number, or whitespace. |
trim |
bool |
True |
Trim surrounding whitespace. |
locale |
str | None |
None |
Per-language override map. |
from slugify_multilang import slugify, SlugifySlugOptions
slugify("Müdür Fu", SlugifySlugOptions(locale="de")) # -> "mueduer-fu"
slugify("Hello World", SlugifySlugOptions(lower=False)) # -> "Hello-World"
Locale overrides exist for: bg, de, es, fr, pt, uk, vi, da, nb, it, nl, sv.
Extending the character map
Mirrors the C# Extend method — registers custom mappings globally at runtime:
from slugify_multilang import extend, slugify
extend({"☂": "umbrella", "₿": "btc"})
slugify("☂ ₿") # -> "umbrella-btc"
How it works
Same five-step pipeline as the C# original:
- NFC normalize (
unicodedata.normalize("NFC", ...)). - Per-character translation — locale override → global charmap → passthrough.
- Remove pass — strip non-URL-friendly characters.
- Strict pass (optional) — keep only Unicode letters, numbers, whitespace.
- Collapse + lowercase — trim, collapse whitespace runs into
replacement, lowercase.
Fidelity
This port is verified against the C# implementation:
- All 583 global charmap entries are byte-for-byte identical.
- All 12 locale override maps are identical.
- The 23-language demo (
python demo.py) produces output identical to the C# demo.
One documented nuance
.NET's regex \w includes Unicode combining marks (\p{Mn}); Python's re \w
does not. This only affects the intermediate remove pass, and only when
strict=False. With the default strict=True, combining marks are stripped by
the strict pass in both implementations, so output is identical.
Demo
python demo.py
Publishing to PyPI
Automated via publish.sh (bash) or publish.ps1
(PowerShell), both mirroring the NuGet workflow in ../publish.ps1.
Requires uv; publish.ps1 additionally needs pwsh
(on macOS: brew install powershell).
Credentials come from your ~/.pypirc ([pypi] section) — read automatically by
twine, so no token is passed on the command line:
./publish.sh # bash — uploads to PyPI using ~/.pypirc
./publish.sh -r testpypi # dry-run against TestPyPI (needs a [testpypi] section)
pwsh ./publish.ps1 # PowerShell equivalent
pwsh ./publish.ps1 -Repository testpypi
The script: reads the current version → checks __init__.py is in sync → runs the
test suite → uv build → uvx twine check → uvx twine upload to pypi.org →
auto-increments the patch version for next time.
twinealso honours theTWINE_USERNAME/TWINE_PASSWORDenv vars if you prefer not to use~/.pypirc.uv publishis not used because it does not read~/.pypirc.
Version-number rule
Same convention as the NuGet package: semver MAJOR.MINOR.PATCH, with the
version stored in pyproject.toml being the one published now. After a
successful publish the patch component is bumped automatically (e.g.
1.0.2 → 1.0.3). pyproject.toml is the single source of truth and the script
keeps slugify_multilang/__init__.py's __version__ in lockstep (it refuses to
publish if the two disagree). Bump MINOR/MAJOR by hand for feature/breaking
releases. The Python and C# packages share the same version line (1.0.2).
Git repository URL rule
The [project.urls] Homepage and Repository point at the same repository as
the C# .csproj (RepositoryUrl / PackageProjectUrl):
https://github.com/balck3py/slugify-multilag
Keep these identical to the C# project so both packages resolve to one source repo.
Project details
Release history Release notifications | RSS feed
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file slugify_multilang-1.0.2.tar.gz.
File metadata
- Download URL: slugify_multilang-1.0.2.tar.gz
- Upload date:
- Size: 25.5 kB
- Tags: Source
- Uploaded using Trusted Publishing? No
- Uploaded via: twine/6.2.0 CPython/3.14.3
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
1cfbc1e44d01a1f9d8fcceb33781bf8fd61a8e383c5fea1e380fac6ac721ac8b
|
|
| MD5 |
0f2501541c1f9dc42b1e1571325c55e6
|
|
| BLAKE2b-256 |
bde23e10747bc22dffb015ed2f06d1eef6304d424c8bd2758f5969fe3a5e7f59
|
File details
Details for the file slugify_multilang-1.0.2-py3-none-any.whl.
File metadata
- Download URL: slugify_multilang-1.0.2-py3-none-any.whl
- Upload date:
- Size: 11.6 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? No
- Uploaded via: twine/6.2.0 CPython/3.14.3
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
47f51dd2581391e31e19ecfd4a498a77ca5380b69481224f92b4338f028ad925
|
|
| MD5 |
b13dca69cb0332c9476a17ced7bee1ea
|
|
| BLAKE2b-256 |
42eb4a33a6bb70d3d67bb8dd95625c9cb706a247bdf72d13876b24472cc9c2a7
|