Software stores data the way machines like it. Users want to read it the way humans like it.
So you write this boilerplate code every day.
You manually split a PascalCase enum into words.
You turn 2 into "2nd" with a few if statements.
You format a timestamp as "3 hours ago". You pluralize "item" only when the count is not 1.
It is a few lines here, a helper method there - and it piles up across a codebase.
Humanizer is a small, free .NET library that does all of it for you.
It turns developer-shaped data into human-friendly text with a single method call.
In this post, we will explore:
- How to Humanize and transform strings
- How to Humanize enums
- How to Humanize dates and times
- How to Turn numbers into words
- How to Work with culture
- How to Pluralize and quantify
- How to Format byte sizes and large numbers
- Bonus: Roman numerals, collections, and fluent dates
Let's dive in.
How to Humanize and Transform Strings
First, add Humanizer to your project by installing the following NuGet package:
bashdotnet add package Humanizer
The core of Humanizer is Humanize(), which turns a developer string into a readable sentence.
It splits strings in PascalCase, camelCase, snake_case, kebab-case, all in lower or UPPER case, into words and applies sentence casing:
csharp"PascalCaseInputStringIsTurnedIntoSentence".Humanize(); // => "Pascal case input string is turned into sentence" "Underscored_input_string_is_turned_into_sentence".Humanize(); // => "Underscored input string is turned into sentence"
Transform changes the casing without splitting words. It takes one or more strategies:
csharp"Sentence casing".Transform(To.TitleCase); // => "Sentence Casing" "Sentence casing".Transform(To.LowerCase); // => "sentence casing" "Sentence casing".Transform(To.SentenceCase); // => "Sentence casing"
Dehumanize is the reverse of Humanize - it turns a sentence back into a PascalCase identifier:
csharp"Pascal case input string is turned into sentence".Dehumanize(); // => "PascalCaseInputStringIsTurnedIntoSentence"
And Truncate shortens text to a length, adding an ellipsis:
csharp"Long text to truncate".Truncate(10); // => "Long text…"
These four cover most of the string formatting you write by hand: generating labels, display names, and identifiers from code.
How to Humanize Enums
Enum names are in PascalCase, and your UI needs readable labels.
Normally, you map each enum member to a label by hand:
csharpvar label = status switch { ShipmentStatus.OutForDelivery => "Out for delivery", ShipmentStatus.InTransit => "In transit", // ... a line for every member };
Calling Humanize() on the enum value does the same thing for free.
It splits the name into words, so the switch disappears:
csharppublic enum ShipmentStatus { Pending, InTransit, OutForDelivery, Delivered } ShipmentStatus.OutForDelivery.Humanize(); // => "Out for delivery" ShipmentStatus.InTransit.Humanize(); // => "In transit"
When you need a label that does not match the member name, add a [Description] attribute and Humanizer uses it:
csharppublic enum ShipmentStatus { [Description("Awaiting pickup")] Pending, // ... } ShipmentStatus.Pending.Humanize(); // => "Awaiting pickup"
You can also go the other way, turning a display string back into the enum value with DehumanizeTo:
csharp"Out for delivery".DehumanizeTo<ShipmentStatus>(); // => ShipmentStatus.OutForDelivery
This alone removes a surprising amount of mapping code from the boundary between your domain and your UI.
How to Humanize Dates and Times
"2 hours ago" reads better than a raw timestamp. Humanizer produces it from a DateTime:
csharpDateTime.UtcNow.AddHours(-2).Humanize(); // => "2 hours ago" DateTimeOffset.UtcNow.AddHours(1).Humanize(); // => "an hour from now"
It compares the date to now and describes the difference in plain language - the relative timestamps you see on every social feed and dashboard.
TimeSpan works the same way, and a precision argument controls how many units it shows:
csharpTimeSpan.FromDays(1).Humanize(); // => "1 day" TimeSpan.FromDays(16).Humanize(precision: 2); // => "2 weeks, 2 days"
So a shipment's estimated delivery window or a job's run time becomes readable without date math on your end.
How to Turn Numbers Into Words
Sometimes a number needs to be spelled out - on an invoice, a cheque, or a generated document.
ToWords does exactly that:
csharp3501.ToWords(); // => "three thousand five hundred and one"
For positions rather than amounts, Ordinalize adds the suffix and ToOrdinalWords spells it out:
csharp1.Ordinalize(); // => "1st" 5.Ordinalize(); // => "5th" 21.Ordinalize(); // => "21st" 1.ToOrdinalWords(); // => "first" 2.ToOrdinalWords(); // => "second"
Getting "1st", "2nd", "3rd", and "21st" right by hand is fiddly - the rules have exceptions. Humanizer knows them.
How to Work with Culture
Humanizer is culture-aware.
It ships with localizations for dozens of languages, so date humanization, ToWords, and number formatting follow the current culture.
When you need a specific culture, pass a CultureInfo explicitly:
csharpusing System.Globalization; var culture = CultureInfo.GetCultureInfo("es"); DateTime.UtcNow.AddHours(-2).Humanize(culture: culture); // => "hace 2 horas" 3501.ToWords(culture: culture); // => "tres mil quinientos uno"
If you only need neutral English resources, you can install the smaller Humanizer.Core package.
For other languages, install the matching Humanizer language package.
How to Pluralize and Quantify
Grammar around counts is full of edge cases: "1 item" but "2 items"; "1 person" but "2 people".
Pluralize and Singularize handle the irregular forms English is famous for:
csharp"Man".Pluralize(); // => "Men" "Men".Singularize(); // => "Man"
Without ToQuantity, you end up writing the familiar ternary by hand:
csharpvar text = $"{count} {(count == 1 ? "case" : "cases")}";
ToQuantity is the one you will reach for most. Give it a number, and it both pluralizes the word and prefixes the count:
csharp"case".ToQuantity(0); // => "0 cases" "case".ToQuantity(1); // => "1 case" "case".ToQuantity(5); // => "5 cases"
Ask for the number as words, and it spells out the count too:
csharp"case".ToQuantity(5, ShowQuantityAs.Words); // => "five cases"
This is the clean replacement for the count == 1 ? "item" : "items" ternaries scattered across your views.
How to Format Byte Sizes and Large Numbers
File sizes and big counts have their own human formatting, and Humanizer covers both.
Its byte-size helpers turn a raw number into the right unit:
csharp(10).Kilobytes().Humanize(); // => "10 KB" (0.5).Kilobytes().Humanize(); // => "512 B" (1024).Kilobytes().Humanize(); // => "1 MB"
For large counts, ToMetric produces the compact "k / M / B" form you see on dashboards and social buttons:
csharp1230d.ToMetric(); // => "1.23k" "1.23k".FromMetric(); // => 1230
Display a download size or a follower count without writing your own unit-scaling logic.
Bonus: Roman Numerals, Collections, and Fluent Dates
Roman numerals convert both ways:
csharp2024.ToRoman(); // => "MMXXIV" "XIV".FromRoman(); // => 14
A collection of strings becomes a readable list - the items separated by commas, with the last joined by "and":
csharpvar carriers = new[] { "FedEx", "UPS", "DHL" }; carriers.Humanize(); // => "FedEx, UPS and DHL"
And the fluent date API builds TimeSpan and DateTime values that read like English:
csharpvar dueDate = 2.Days().FromNow(); // the DateTime two days from now var window = 2.Days() + 3.Hours(); // a TimeSpan of 2 days and 3 hours
None of these are things you reach for daily, but when you need them, each one saves you a helper class.
Summary
Humanizer is a small dependency that removes an entire category of code you would otherwise write and maintain by hand.
Let's recap the key takeaways:
- Readable text from code shapes.
Humanize,Transform, andDehumanizeconvert PascalCase strings and enum members into display labels and back. - Human-friendly dates and numbers.
DateTime.Humanize()gives you "2 hours ago",TimeSpan.Humanize()reads timespans in plain words, andToWordsandOrdinalizespell numbers out. - Correct grammar around counts.
Pluralize,Singularize, andToQuantityhandle irregular plurals and the "1 item vs 2 items" problem for you. - Sizes and big numbers. The byte-size helpers and
ToMetricformat file sizes and large counts the way users expect.
None of this is hard to write yourself (because of edge cases).
The point is that you should not have to - it is a solved problem, the rules have edge cases, and Humanizer has them covered.
Reach for it the next time you find yourself formatting data for a human to read.
Hope you find this newsletter useful. See you next time.


Comments