Skip to main content
Version: 3.0.10 (latest)

Migrate from 2.14.1 to Humanizer 3

Use this guide to move from the final stable Humanizer 2 release, 2.14.1, to 3.0.10. Start from a clean 2.14.1 build, make the package and namespace changes, rebuild every Humanizer-consuming assembly, and review each exact output contract your application uses.

Humanizer 3 first shipped as stable 3.0.1; there was no stable 3.0.0. Target 3.0.10, because later 3.x patches restored APIs and fixed analyzer and fallback-package defects present in early 3.x builds.

Upgrade checklist

  1. Record every direct and transitive Humanizer* package reference, target framework, centrally pinned dependency, and restore SDK.
  2. Upgrade restore tooling to .NET SDK 9.0.200 or later when using the aggregate Humanizer metapackage.
  3. Replace obsolete locale package IDs and retarget unsupported frameworks.
  4. Install 3.0.10, run the HUMANIZER001 namespace code fix, and rebuild all callers.
  5. Repair the remaining API and extensibility errors from the matrix below.
  6. Compare exact output for every used operation and culture, especially when Humanizer text is persisted or used as an identifier.

Consolidate namespaces

Run HUMANIZER001 to replace the old Humanizer.* subnamespace imports and qualified names, then rebuild every caller. The first matrix row covers the reflection, serialization, and precompiled-binary cases that need manual work.

Package, API, and metadata breaks

The only exact shared assembly target is netstandard2.0.

VersionHumanizer.Core assets
2.14.1netstandard1.0, netstandard2.0, net6.0
3.0.10netstandard2.0, net48, net8.0, net10.0

Assembly identity changes from Humanizer, Version=2.14.0.0 to Humanizer, Version=3.0.0.0; the public key token remains 979442b78dfc278e.

Affected caller or surfaceBreak and migration from 2.14.1 to 3.0.10Evidence
Imports, qualified names, precompiled callers, reflection, or persisted type names under Humanizer.Bytes, .Configuration, .DateTimeHumanizeStrategy, .Inflections, and .LocalisationTypes move from subnamespace identities such as Humanizer.Bytes.ByteSize to Humanizer; source gets CS0234, old binaries can throw TypeLoadException, and persisted type names stop resolving. Run HUMANIZER001, update reflection/serialization maps, rebuild every caller, and exercise plugin loading.PR #1351
StringExtensions.FormatWith callers"{0:N2}".FormatWith(culture, value) compiles in 2.14.1. The type and overloads are removed in 3.0.10, causing CS1061 or binary type-load failure. Use string.Format(culture, format, args) and rebuild.PR #1395
Boolean ToMetric overload callersToMetric(value, hasSpace, useSymbol, decimals) exists in 2.14.1. The overloads are removed in 3.0.10; named calls can get CS1739. Use MetricNumeralFormats.WithSpace and, when needed, UseName; test boundary output.PR #1389
Code referencing EnglishArticlesPublic enum values A, An, and The exist in 2.14.1. The enum is removed in 3.0.10, causing CS0103, missing-type reflection, or binary failure. Replace enum-dependent application logic with an application-owned representation.PR #1443
Configurator.EnumDescriptionPropertyLocator callersThe public settable property can be assigned after earlier calls in 2.14.1. It is no longer public in 3.0.10, and UseEnumDescriptionPropertyLocator throws if the locator was already resolved. Register the locator once during startup, before affected enum humanization; test startup plus the first call.PR #1434
Boxed Enum callers and generic enum wrappersHumanize(this Enum) exists in 2.14.1 and DehumanizeTo<T> has weaker constraints. In 3.0.10 Humanize is generic and both paths require where T : struct, Enum, causing CS0453 or CS0314. Preserve the concrete enum type, add the enum constraint, or use the runtime-Type dehumanize overload.PR #1409
Custom IFormatter implementations and DefaultFormatter subclassesIn 2.14.1 there is no age member and the protected number hook is Format(string, int, bool). In 3.0.10 IFormatter requires TimeSpanHumanize_Age() and the hook adds TimeUnit, causing CS0535 or CS0115. Implement the age member and update overrides to accept TimeUnit; compile and exercise each custom formatter.PR #1068
new DefaultFormatter(null)The null literal selects the only string constructor in 2.14.1. A CultureInfo constructor in 3.0.10 makes it ambiguous, causing CS0121. Pass a non-null culture/code, or cast deliberately after reviewing the null contract.PR #1468
Reflection serializers that invoke nonpublic setters on ByteSize or ByteRateByteSize properties and ByteRate.Size/Interval have private setters in 2.14.1 metadata. They are getter-only in 3.0.10, so GetSetMethod(true) returns null. Use public constructors/factories or a serializer converter; test round trips.PR #1340
Nullable-enabled, warnings-as-errors callersPublic metadata is nullable-oblivious in 2.14.1, so string? value; value.Humanize() compiles without a nullable warning. Published 3.0.10 annotations make the same source fail CS8604. Prove non-null at the call boundary and align override annotations; do not suppress without an invariant.PR #1437
netstandard1.0 projectsA compile/runtime asset exists in 2.14.1. With 3.0.10 restore can succeed without a Humanizer compile asset, then compilation fails CS0246. Retarget to netstandard2.0 or a supported runtime TFM and inspect project.assets.json.PR #1322
net6.0/net7.0 callers of Humanizer DateOnly/TimeOnly APIsThe dedicated 2.14.1 net6 asset exposes DateOnly.Humanize, TimeOnly.Humanize, DateOnly.ToOrdinalWords, and TimeOnly.ToClockNotation. The 3.0.10 netstandard2.0 fallback cannot expose those BCL types, so identical source gets CS1929/CS1061. Retarget to net8.0+ or replace the unavailable calls; compile the exact target framework.TFM removal
Other net6/net7 callers with warnings as errorsThe dedicated 2.14.1 net6 asset has no dependency support warning. The 3.0.10 netstandard2.0 fallback brings Immutable 9.0.10, whose target warns that net6 is unsupported; warnings-as-errors fails. Prefer net8+. Suppress SuppressTfmSupportBuildWarnings only after testing the unsupported combination.PR #1408
netstandard2.0/net48 projects pinning dependencies below the v3 floorHumanizer 2 has no minimum for Immutable, Annotations, or Memory. Humanizer 3 requires System.Collections.Immutable >= 9.0.10, System.ComponentModel.Annotations >= 5.0.0, and System.Memory >= 4.6.3; lower pins get NU1605. Remove or raise the pins and inspect the restored graph on each TFM.3.0.10 nuspec
Aggregate Humanizer restores on older NuGet clientsSDK 8.0.419 restores 2.14.1 but fails 3.0.10 with NU1202 for Humanizer.Core.fil; current tooling succeeds. Use SDK 9.0.200+ or reference exact Humanizer.Core plus only needed locale packages.NuGet.Client #6124
Direct locale package references2.14.1 IDs include Humanizer.Core.bn-BD, .fi-FI, Humanizer.Core.fil-PH, .ko-KR, .ms-MY, .nb-NO, .th-TH, and Humanizer.Core.fr-BE. No 3.0.10 exists under those IDs, so restore gets NU1102. Use .bn, .fi, .fil, .ko, .ms, .nb, .th; use .fr and verify fr-BE fallback. Humanizer.Core.fil requires NuGet 17.13/.NET SDK 9.0.200+ because older clients reject it with NU1202.2.14.1 fil-PH
3.0.10 fil
PR #1591

Core behavior and exception breaks

These are output or control-flow changes. They matter only to callers that use the affected operation, but they are breaking when text is asserted, persisted, parsed, or used as a key.

Affected caller or surface2.14.13.0.10 example or symptomMigration and verificationEvidence
Pascalize/Camelize identifierscustomer-first-name changes from Customer-first-name/customer-first-name to CustomerFirstName/customerFirstName; digit-segment handling also changes.Version legacy persisted-name transforms or migrate stored names deliberately; recompile serializers and schema generators.PR #1299
Dehumanize with a trailing digit"everything0".Dehumanize() changes from Everything0 to Everything 0 because it passes through the changed Pascalize path.Rebaseline display output or preserve the legacy transform for persisted identifiers.PR #1299
Dehumanize on spaced hyphens or whitespacecustomer - first - name: CustomerFirstNameCustomer First Name; three spaces: empty → three spaces.Trim/normalize first or retain a legacy key converter; plain customer-first-name is unchanged.PR #1633
Titleize with no recognized letters"@@" and "?": empty → original punctuation. Humanize remains empty in both versions.Rebaseline or apply an explicit application validation rule.PR #1611
Humanize(LetterCasing.Sentence) with punctuationI and no one elseI, and no one else; semicolons are also retained.Rebaseline or strip punctuation explicitly where the old result is contractual.PR #1185
English ToQuantity(-1)-1 cases/-1 hours-1 case/-1 hour; -2 and -0.5 remain plural.Rebaseline the exact -1 case.PR #1548
Cross-year DateOnly.Humanize2022-03-01 relative to 2024-03-01: yesterday2 years ago; 2025-03-01: tomorrowone year from now.Rebaseline; use date arithmetic rather than prose for business logic.PR #1228
English inflection of s/SEmpty/exception-prone singularization and unchanged plural become s/S singular and ss/Ss plural.Rebaseline or register a domain rule.PR #1178
English Pluralize("was")waswere.Rebaseline or register an explicit irregular rule.PR #1244
English vocabulary for apparatus, pliers, lens, chassis, debrisIncorrect generic forms become corrected forms such as apparatus, pliers, lenses, chassis, and debris.Rebaseline and add domain-specific vocabulary only where needed.PR #1356
English article prefix/suffix round tripThe General Theory of Relativity round-trips as Theory of Relativity The General → the original phrase.Update ordering and snapshot expectations.PR #1421
ToMetric at a rounded threshold999500d with zero decimals: 1000k1M.Rebaseline every used metric boundary.PR #1570
Registry mutation after first resolutionA later formatter/converter registration was accepted → throws InvalidOperationException after the registry freezes.Register every customization during startup, before the first resolution; test startup ordering.PR #1635
Multiple custom CultureInfo instances sharing a nameEach instance could retain a distinct format → v3 caches by culture name, so later instances reuse the first formatter.Reuse one culture instance or avoid same-name divergent formats; probe both instances.PR #1637
ByteSize.TryParse with an out-of-range bit countA huge bit value is accepted/clamped → parsing returns false and zero.Treat the invalid input as a parse failure and update error handling.PR #1595

Localized output breaks

Run culture-specific golden tests for every operation you use. The verified corpus compared 53 cultures and 105 calls per version; newly activated locales are additive and are not listed as breaks.

Affected output2.14.13.0.10 exampleEvidence
Bulgarian durationsOne day един ден1 ден; word forms become localized and gendered.PR #1460
Russian and Ukrainian durationsодин день1 день, with related word-form corrections.PR #1445, PR #1449
Japanese year duration1 年間の1 年.PR #1082
Portuguese relative time3 horas atráshá 3 horas; em 3 horasdaqui a 3 horas.PR #1498
Chinese “now”今天现在 for simplified Chinese and 現在 for traditional Chinese.PR #1239
Finnish relative time and durationsEnglish fallback such as tomorrow → Finnish such as huomenna.PR #1300
Italian word-form durationsone dayun giorno.PR #1428
Hungarian duration and number outputFallback/word output becomes Hungarian and some non-word units become numeric.PR #1501
Croatian missing relative-time key+3 days throws ArgumentException → returns 3 days from now via the neutral fallback.PR #1426
Arabic negative numbersناقص واحدسالب واحد.PR #1539
Bulgarian number wordsединедно; хилядаедна хиляда.PR #1460
German number wordseineins; einhunderteineinhunderteins.PR #1536
Croatian number words- jedanminus jedan; devetdesetdevedeset.PR #1250
Russian/Ukrainian negative numbers below 1000A partial минус becomes a complete phrase such as минус один.PR #1461
Vietnamese negative numbersIndexOutOfRangeException → valid Vietnamese output beginning trừ.PR #1569

Example

// Humanizer 2.14.1
using Humanizer;
using Humanizer.Configuration;
var text = "{0:N2}".FormatWith(culture, value);

// Humanizer 3.0.10
using Humanizer;
var text = string.Format(culture, "{0:N2}", value);

Pitfalls

  • Do not suppress HUMANIZER001 before running its code fix. It covers the namespace move only; remaining compiler errors are real API changes.
  • Do not infer a break from an early 3.x prerelease or patch. ToQuantity(int) and the first-word title-case behavior are present/correct in 3.0.10.
  • Do not treat all net6 consumption as broken. General callers can select the netstandard2.0 asset; the DateOnly/TimeOnly surface and dependency warning are the verified net6/net7 boundaries.
  • Enum dehumanization remains case-insensitive despite contradictory 3.x XML wording.

Verification

dotnet restore <solution> --force-evaluate
dotnet list <project> package --include-transitive
dotnet build <solution>
dotnet test <solution>

Inspect obj/project.assets.json for every target framework. Exercise plugins, custom formatters, serializers, registry startup order, persisted identifiers, and each culture-specific exact-output contract before release.

Incidental resource implementation surface

Resources and ResourceKeys remain in stable Humanizer 3 releases through 3.0.10, but they were resource plumbing rather than an intended application contract. For completeness, some plural-form keys disappear and some retained values change; direct callers can therefore get ArgumentException or different text. Move such code to formatter APIs. See PR #1427.

This page is frozen in the Humanizer 3.0.10 documentation snapshot.