Case Converter — C# source
Convert text between UPPER, lower, Title, Sentence, camelCase, PascalCase, snake_case, kebab-case, CONSTANT_CASE and slug. Live, one-click copy.
This is the C# implementation — the same logic the interactive tool runs, in a shareable, citable form.
// case-converter — pure, deterministic text-case transformations.
//
// Language: C# 12 / .NET 8 (standard library only)
// Source: CosmoDev polyglot showcase port of the Case Converter tool.
// Canonical: src/lib/case.ts (the live TypeScript) plus this
// tool's python.py / rust.rs reference ports.
// License: display source — part of CosmoDev's polyglot tool pages.
//
// Split any input into word tokens and re-emit it in upper / lower / title /
// sentence / camel / pascal / snake / kebab / constant / slug case — same
// inputs produce the same outputs as the TypeScript original, including edge
// cases. Case mapping is Unicode-aware and culture-invariant
// (ToUpperInvariant / ToLowerInvariant), matching JavaScript's
// toUpperCase / toLowerCase without locale surprises like the Turkish i.
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text.RegularExpressions;
namespace CosmoDev.CaseConverter;
/// <summary>Pure, deterministic text-case transformations.</summary>
public static class CaseConverter
{
// --- Compiled patterns ----------------------------------------------------
// Each mirrors a regex from the TypeScript reference.
// A camelCase seam: a lowercase letter or digit immediately followed by an
// uppercase letter. We splice a space in here before splitting.
private static readonly Regex CamelBoundary = new(@"([a-z0-9])([A-Z])");
// Word delimiters: any run of whitespace (.NET \s is Unicode), underscore,
// hyphen, or dot.
private static readonly Regex Separators = new(@"[\s_\-.]+");
// Anything that is not an ASCII letter or digit — stripped from each token.
private static readonly Regex NonAlnum = new(@"[^a-zA-Z0-9]");
// A title "word": a maximal run that starts with an ASCII word character
// ([A-Za-z0-9_] — spelled out because .NET \w is Unicode-wide, unlike
// JavaScript's) and continues with non-whitespace.
private static readonly Regex TitleWord = new(@"[A-Za-z0-9_]\S*");
/// <summary>
/// Tokenize <paramref name="s"/> into word units.
///
/// The pipeline reproduces the TypeScript reference step for step:
/// 1. Insert a space at every camelCase boundary.
/// 2. Split on runs of whitespace / underscore / hyphen / dot.
/// 3. Strip remaining non-alphanumerics and drop empty tokens.
/// </summary>
public static List<string> Words(string s) =>
Separators.Split(CamelBoundary.Replace(s, "$1 $2"))
.Select(part => NonAlnum.Replace(part, ""))
.Where(cleaned => cleaned.Length > 0)
.ToList();
/// <summary>Upper-case every character.</summary>
public static string Upper(string s) => s.ToUpperInvariant();
/// <summary>Lower-case every character.</summary>
public static string Lower(string s) => s.ToLowerInvariant();
/// <summary>Capitalize the first character and lowercase the rest.</summary>
public static string Cap(string s) =>
s.Length == 0 ? s : char.ToUpperInvariant(s[0]) + s[1..].ToLowerInvariant();
/// <summary>
/// Title case: capitalize the first character of each maximal word-run.
/// Characters between runs (spacing, punctuation) are left untouched.
/// </summary>
public static string Title(string s) => TitleWord.Replace(s, m => Cap(m.Value));
/// <summary>
/// Sentence case: lowercase + trim the whole string, then uppercase only
/// the first character. Empty input yields empty output.
/// </summary>
public static string Sentence(string s)
{
string t = s.ToLowerInvariant().Trim();
return t.Length == 0 ? t : char.ToUpperInvariant(t[0]) + t[1..];
}
/// <summary>camelCase: the first word stays lowercase, every later word is capitalized.</summary>
public static string Camel(string s) => string.Join("",
Words(s).Select((w, i) => i == 0 ? w.ToLowerInvariant() : Cap(w)));
/// <summary>PascalCase: every word capitalized and concatenated.</summary>
public static string Pascal(string s) => string.Join("", Words(s).Select(Cap));
/// <summary>snake_case — "_".join(words).lower() in the reference.</summary>
public static string Snake(string s) => string.Join("_", Words(s)).ToLowerInvariant();
/// <summary>kebab-case — "-".join(words).lower() in the reference.</summary>
public static string Kebab(string s) => string.Join("-", Words(s)).ToLowerInvariant();
/// <summary>CONSTANT_CASE — "_".join(words).upper() in the reference.</summary>
public static string Constant(string s) => string.Join("_", Words(s)).ToUpperInvariant();
/// <summary>slug (kebab's URL-safe twin) — "-".join(words).lower() in the reference.</summary>
public static string Slug(string s) => string.Join("-", Words(s)).ToLowerInvariant();
// The case-transform registry: CaseId -> pure function. Keys mirror the
// TypeScript `caseTransforms` object 1:1 (insertion order preserved).
private static readonly Dictionary<string, Func<string, string>> CaseTransforms = new()
{
["upper"] = Upper,
["lower"] = Lower,
["title"] = Title,
["sentence"] = Sentence,
["camel"] = Camel,
["pascal"] = Pascal,
["snake"] = Snake,
["kebab"] = Kebab,
["constant"] = Constant,
["slug"] = Slug,
};
/// <summary>Read-only view of the registry (the TS object's public surface).</summary>
public static IReadOnlyDictionary<string, Func<string, string>> Transforms => CaseTransforms;
/// <summary>
/// Apply a named case transform. Throws <see cref="ArgumentException"/>
/// when <paramref name="caseId"/> is not a known case.
/// </summary>
public static string Convert(string caseId, string s) =>
CaseTransforms.TryGetValue(caseId, out var fn)
? fn(s)
: throw new ArgumentException($"Unknown case: {caseId}", nameof(caseId));
/// <summary>Like <see cref="Convert"/> but returns null for an unknown case id.</summary>
public static string? TryConvert(string caseId, string s) =>
CaseTransforms.TryGetValue(caseId, out var fn) ? fn(s) : null;
}
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 →