Slugify — C# source
Generate clean, URL-safe slugs from any text with locale-aware Unicode transliteration. Accents, emoji, and punctuation are handled automatically - runs entirely in your browser.
This is the C# implementation — the same logic the interactive tool runs, in a shareable, citable form.
// slugify — C# port: URL-safe slugs with locale-aware Unicode transliteration.
using System;
using System.Collections.Generic;
using System.Text;
/// <summary>Pure slug logic, ported from the canonical TS lib. Deterministic and
/// total: any input yields a slug, never an exception.</summary>
public static class Slugify
{
/// <summary>Letter casing for the produced slug.</summary>
public enum SlugCase { Lower, Preserve, Upper }
/// <summary>Options mirror the TS SlugifyOptions; every field defaults.</summary>
public sealed class Options
{
public string Separator = "-";
public int MaxLength; // <= 0 = unlimited
public SlugCase Case = SlugCase.Lower;
public bool StripStopwords;
}
// Letters/ligatures NFKD does not decompose into an ASCII base + combining
// mark. Accented Latin (á é ñ …) needs no entry: FormKD splits it and the
// U+0300..U+036F strip below drops the diacritic.
private static readonly Dictionary<char, string> Translit = new()
{
['ß'] = "ss",
['æ'] = "ae", ['Æ'] = "ae", ['œ'] = "oe", ['Œ'] = "oe",
['ff'] = "ff", ['fi'] = "fi", ['fl'] = "fl", ['ffi'] = "ffi", ['ffl'] = "ffl", ['ſt'] = "st", ['st'] = "st",
['ð'] = "d", ['Ð'] = "d", ['þ'] = "th", ['Þ'] = "th", ['ø'] = "o", ['Ø'] = "o",
['ł'] = "l", ['Ł'] = "l", ['đ'] = "d", ['Đ'] = "d", ['ħ'] = "h", ['Ħ'] = "h",
};
private static readonly HashSet<string> Stopwords = new()
{
"the", "a", "an", "and", "or", "but", "of", "to", "in", "on", "at",
"for", "with", "by", "from",
};
private static bool IsAsciiAlnum(char c) =>
(c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z') || (c >= '0' && c <= '9');
/// <summary>Break text into clean ASCII words (transliterated, diacritics
/// stripped, cased). Mirrors the TS tokenize().</summary>
private static List<string> Tokenize(string text, Options o)
{
var t = new StringBuilder(text.Length);
foreach (var ch in text) t.Append(Translit.TryGetValue(ch, out var rep) ? rep : ch.ToString());
var ascii = new StringBuilder();
foreach (var ch in t.ToString().Normalize(NormalizationForm.FormKD))
{
if (ch >= '̀' && ch <= 'ͯ') continue; // combining diacritics
ascii.Append(IsAsciiAlnum(ch) ? ch : ' '); // collapse runs to spaces
}
var words = new List<string>();
var cur = new StringBuilder();
foreach (var ch in ascii.ToString().Trim().ToCharArray())
{
if (ch == ' ') { if (cur.Length > 0) { words.Add(cur.ToString()); cur.Clear(); } }
else cur.Append(ch);
}
if (cur.Length > 0) words.Add(cur.ToString());
for (var i = 0; i < words.Count; i++)
words[i] = o.Case switch
{
SlugCase.Upper => words[i].ToUpperInvariant(),
SlugCase.Lower => words[i].ToLowerInvariant(),
_ => words[i],
};
if (o.StripStopwords)
words.RemoveAll(w => Stopwords.Contains(w.ToLowerInvariant()));
return words;
}
// Truncate to max chars at the last whole-word boundary (hard cut when the
// separator is empty or absent from the head).
private static string TruncateAtWord(string slug, string separator, int max)
{
if (slug.Length <= max) return slug;
var cut = slug.Substring(0, max);
if (separator.Length == 0) return cut;
var last = cut.LastIndexOf(separator, StringComparison.Ordinal);
return last > 0 ? cut.Substring(0, last) : cut;
}
/// <summary>Convert arbitrary text into a URL-safe slug.</summary>
public static string SlugifyText(string text, Options? o = null)
{
o ??= new Options();
var slug = string.Join(o.Separator, Tokenize(text, o));
return o.MaxLength > 0 ? TruncateAtWord(slug, o.Separator, o.MaxLength) : slug;
}
/// <summary>Slugify each line independently (batch mode).</summary>
public static List<string> SlugifyLines(string text, Options? o = null)
{
o ??= new Options();
var out_ = new List<string>();
foreach (var line in text.Split(new[] { "\r\n", "\n" }, StringSplitOptions.None))
out_.Add(SlugifyText(line, o));
return out_;
}
}
Also available in 13 other languages
Every CosmoDev tool ships its pure logic in TypeScript (web) and Go (CLI), with authored implementations in a dozen-plus languages — the same contract, ported. Compare all languages side by side →