Upgrade from Humanizer 3.0.10 to Humanizer 4
August 2, 2026 · View on GitHub
Use this guide to move an application, library, build tool, or custom Humanizer
extension from 3.0.10 to Humanizer 4. It covers migration work: package
replacement, source breaks, and intentional output changes. New opt-in features
belong in What's New, and the complete change history belongs in the
GitHub releases.
:::caution Release candidate boundary
Humanizer 4 is not a stable release: no v4 tag, GitHub Release, or public NuGet
package exists. This guide was audited against live-main commit
087cde3f5de637a2a8e6bbaacd7451a9f5f28e28.
The latest retained main-branch artifact at audit time is the signed
4.0.0-preview.51 package from Azure build
130178,
built from that exact commit.
Replace <humanizer-4-version> below with the candidate you are testing, and
repeat this audit against the final tag before release.
Remaining release work can still change the public surface. Reconcile this guide against the final public API and package diff after that work lands.
:::
Who must migrate
Work through the complete guide if any of these apply:
- the dependency graph contains
Humanizer.Coreor aHumanizer.Core.<locale>package; - an
intreceives the result ofToNumber,TryToNumber, orIWordsToNumberConverter; - a formatter subclass overrides the protected resource-key hooks on
DefaultFormatter; - calls omit
CultureInfowhileCurrentCultureandCurrentUICulturecan differ; - tests, persisted labels, URLs, identifiers, or serialized output depend on exact Humanizer strings.
An application that starts directly on Humanizer 4 has no 3.0.10 compatibility work. Review the current feature documentation instead.
Upgrade checklist
- Create a migration branch and make the
3.0.10build and tests green. - Record every direct and transitive
Humanizer*package reference. - Remove
Humanizer.Coreand everyHumanizer.Core.<locale>reference. - Add only
Humanizerat the selected Humanizer 4 candidate version. - Delete stale restore outputs or regenerate the lock file, then restore.
- Build all consumers and fix the public API changes below.
- Run the words-to-number compiler code fix where it applies.
- Compare exact string output for every Humanizer operation and culture the application uses.
- Run the validation checklist on every supported target framework and build host.
Example
Package before and after
Humanizer 3.0.10 publishes a metapackage graph. Humanizer depends on 51
locale packages; each locale package depends on Humanizer.Core, which carries
the runtime and analyzers. The Humanizer 4 candidate publishes one Humanizer
package. Its assemblies contain the generated locale data, and the package
also contains the Roslyn 3.8, 4.8, and 4.14 analyzer variants.
<!-- 3.0.10: either the metapackage ... -->
<PackageReference Include="Humanizer" Version="3.0.10" />
<!-- ... or a manually selected core and locale graph -->
<PackageReference Include="Humanizer.Core" Version="3.0.10" />
<PackageReference Include="Humanizer.Core.fr" Version="3.0.10" />
<!-- Humanizer 4: replace both forms with one reference -->
<PackageReference Include="Humanizer" Version="<humanizer-4-version>" />
| Migration detail | What to do |
|---|---|
| Affected callers | Projects with direct Humanizer.Core or Humanizer.Core.<locale> references, central package pins, lock files, or allowlists for the old package IDs. |
| Before | Runtime, locale satellite assemblies, and analyzers arrive through a metapackage graph. |
| After | One Humanizer package supplies the runtime, generated locales, analyzers, and buildTransitive/Humanizer.targets. |
| Symptom | Leaving old references in place can select both the 3.x and 4.x graphs, retaining stale runtime or analyzer assets and producing restore or build conflicts. |
| Minimal action | Remove every old core/locale reference and version pin. Add one Humanizer 4 reference and regenerate restore outputs. |
| Verify | Run dotnet list <project> package --include-transitive; the result must contain Humanizer at one version and no Humanizer.Core* package. Inspect obj/project.assets.json when central package management or lock files hide the source. |
| Evidence | The 3.0.10 metapackage specification and the Humanizer 4 pack inputs. |
For netstandard2.0 and net48, the candidate raises its
System.Collections.Immutable dependency from 9.0.10 to 10.0.10;
System.ComponentModel.Annotations remains 5.0.0 and System.Memory remains
4.6.3. A centrally managed lower pin can produce NU1605. Remove that pin or
align it with the resolved Humanizer dependency, then inspect the restore graph.
Precompiled Humanizer consumers also need rebuilding. The strong-named
candidate assembly has version 4.0.0.0, compared with 3.0.0.0 in Humanizer
3.0.10; the public key token remains 979442b78dfc278e.
| Migration detail | What to do |
|---|---|
| Affected callers | Plugins, extensions, or libraries that consume Humanizer but are deployed as precompiled binaries, especially .NET Framework applications. |
| Before | The binary references strong-named Humanizer, Version=3.0.0.0. |
| After | The candidate binary is Humanizer, Version=4.0.0.0, with removed members and widened words-to-number signatures. |
| Symptom | Assembly-load, missing-type, or missing-member failures can occur before application code reaches the changed call site. |
| Minimal action | Rebuild every Humanizer-consuming binary against the selected candidate; do not carry forward a 3.x binding redirect as the migration. |
| Verify | Inspect the final output and dependency manifest, then load and exercise each plugin or extension on every deployment target. No Humanizer 3.x assembly may remain. |
| Evidence | Assembly metadata from the published 3.0.10 package and the package built from the candidate version configuration. |
Target frameworks and toolchain
No 3.0.10 target framework is removed. Humanizer 4 adds a net11.0 asset.
| Version | Package assets |
|---|---|
3.0.10 | netstandard2.0, net48, net8.0, net10.0 |
| Humanizer 4 candidate | netstandard2.0, net48, net8.0, net10.0, net11.0 |
Existing net6.0 and net7.0 consumers continue to select the
netstandard2.0 asset. Do not retarget an application merely to upgrade
Humanizer. If the application targets net11.0, use an SDK that supports that
target; otherwise keep the application's current target and SDK policy.
The Humanizer 4 package retains the three Roslyn-versioned analyzer assets used
by 3.0.10. Older build hosts still use the packaged fallback target. Verify
every CI image and IDE because analyzer selection is a build-host concern, not
a runtime TFM concern.
Building Humanizer itself at the audited candidate uses the SDK pinned in
global.json (11.0.100-preview.6.26359.118); 3.0.10 used 10.0.100.
Those are contributor toolchains, not consumer minimums. A consuming project
only needs an SDK compatible with its own target framework and build policy.
Public API changes
Change words-to-number results from int to long
ToNumber, both TryToNumber overloads, and all three
IWordsToNumberConverter members now use long.
// 3.0.10
int value = words.ToNumber(culture);
bool parsed = words.TryToNumber(out int result, culture);
// Humanizer 4 when the application accepts the wider contract
long value = words.ToNumber(culture);
bool parsed = words.TryToNumber(out long result, culture);
// Humanizer 4 when the application contract must remain Int32
int legacyValue = checked((int)words.ToNumber(culture));
The widened contract also changes an existing overflow result: the phrase
three billion wrapped to -1294967296 in 3.0.10; Humanizer 4 returns
3000000000. Treat that as an intentional behavior change even when source
already used var.
| Migration detail | What to do |
|---|---|
| Affected callers | Explicit int assignments, out int declarations, method return values, generic constraints, mocks, and custom IWordsToNumberConverter implementations. |
| Before | Parser APIs return int; converter implementations expose int members. |
| After | Parser APIs return long; implementations must expose matching long members. |
| Symptom | Typical compiler failures are CS0029, CS0266, CS1503, CS0535, or CS0738. |
| Minimal action | Use long or var. Keep int only at an application boundary and narrow with checked((int)...). Update custom converter signatures and their overflow handling. |
| Verify | Test zero, negative values, int.MaxValue, int.MaxValue + 1L, and the largest phrase the application accepts. Confirm a checked 32-bit boundary throws instead of wrapping. |
| Evidence | The 3.0.10 int API and the candidate long API. |
Update custom formatter resource hooks
The protected string-keyed Format and GetResourceKey hooks on
DefaultFormatter are removed. Move custom behavior to the typed interface
that owns the output.
For application-wide customization, implement the typed interface such as
IFormatter, ICollectionFormatter, INumberToWordsConverter, or
IOrdinalizer, then register that implementation during startup:
Configurator.Formatters.Register("en-custom", customFormatter);
| Migration detail | What to do |
|---|---|
| Affected callers | DefaultFormatter subclasses that override the removed protected methods. |
| Before | Four protected virtual string-key hooks can customize resource-backed output. |
| After | Typed formatter and converter interfaces own customization. |
| Symptom | CS0115, or a precompiled subclass loads but its old override is no longer reached. |
| Minimal action | Move custom behavior to the narrowest typed interface and register it before the registry is first resolved. |
| Verify | Exercise application startup and the first call through every replaced custom formatter path. |
| Evidence | The 3.0.10 formatter hooks, compared with the candidate's typed formatter surface. |
Beyond the incidental resource plumbing noted near the end, the audited public
API baselines show no other removal or signature replacement between 3.0.10
and this candidate. Additive Humanizer 4 APIs are intentionally not listed here.
Behavioral changes
Recheck identifier and title transformations
Existing string transformations deliberately correct separator, punctuation, title-case, and culture-dependent casing behavior:
| Call | 3.0.10 | Humanizer 4 candidate |
|---|---|---|
"Research&Development".Humanize() | Research development | Research & development |
"thisIs3.5mmLong".Titleize() | This Is 3 5mm Long | This Is 3.5mm Long |
"customer name 1".Pascalize() | CustomerName 1 | CustomerName1 |
"everything0".Dehumanize() | Everything 0 | Everything0 |
"some.text.here".Pascalize() | Some.text.here | SomeTextHere |
"__customer_name".Camelize() | customerName | __customerName |
"IstanbulInput".Underscore() under tr-TR | ıstanbul_ınput | istanbul_input |
| Migration detail | What to do |
|---|---|
| Affected callers | Exact-output assertions and code that uses Humanize, Titleize, Pascalize, Camelize, Dehumanize, or Underscore output as identifiers, URLs, cache keys, database keys, or persisted labels. |
| Before | Ampersands are dropped, some separators survive identifier conversion, Dehumanize can insert a space before a trailing digit, and identifier casing follows the ambient culture. |
| After | Ampersands are tokens, spaces and dots are identifier boundaries, Dehumanize keeps the digit attached, and identifier casing is culture-invariant. |
| Symptom | Snapshot failures, changed generated identifiers, or a lookup miss against a previously persisted Humanizer-derived value. |
| Minimal action | Rebaseline display-only output. For durable identifiers, preserve the old value in application code or run an explicit data migration instead of silently regenerating keys. |
| Verify | Compare representative punctuation, numeric suffixes including Dehumanize, acronyms, and Turkish/Azeri casing under every operation the application uses. |
| Evidence | Candidate tests for ampersands, identifier transformations, and the Pascalize path used by Dehumanize. |
Preserve authored enum metadata casing
Enum.Humanize(LetterCasing) now applies the requested casing only to text
derived from the enum member name. Description and Display metadata is
authored display text and is returned unchanged, including inside a flags
combination.
| Call | 3.0.10 | Humanizer 4 candidate |
|---|---|---|
Metadata "SpaceX and the Moon" with LetterCasing.Title | Spacex and the Moon | SpaceX and the Moon |
Metadata flag "SpaceX" combined with name-derived NameDerived, title case | metadata and names are recased | SpaceX and Name Derived |
| Migration detail | What to do |
|---|---|
| Affected callers | Enum and flags humanization that supplies LetterCasing for members with Description or Display metadata. |
| Before | Humanizer applies the requested casing to both metadata and name-derived text. |
| After | Authored metadata keeps its casing; name-derived text still receives the requested casing. |
| Symptom | Capitalization changes in labels and exact-output tests without a compile error. |
| Minimal action | Put the final desired casing in the metadata. Call ApplyCase explicitly afterward only when blanket recasing is an application requirement. |
| Verify | Cover metadata-backed, name-derived, and mixed flags values for every requested casing. |
| Evidence | The candidate enum metadata tests and mixed-flags test; 3.0.10 unconditionally applies casing. |
Recheck relative date output
Existing date operations intentionally change their text:
| Call | 3.0.10 | Humanizer 4 candidate |
|---|---|---|
date.Humanize(date) for DateOnly | now | localized date-only wording such as today |
A relative DateTime 14 days away | 14 days from now | 2 weeks from now |
A precision-relative DateTime 90 days ago | 2 months ago | 3 months ago |
A precision-relative DateTime 730 days ago | one year ago | 2 years ago |
| Migration detail | What to do |
|---|---|
| Affected callers | Exact relative-date assertions, persisted phrases, UI copy review, and custom formatters that supply date phrases. |
| Before | Equal DateOnly values reuse instant wording; distances below 28 days use days; exact month/year multiples can be rounded down. |
| After | Equal dates use a date-only phrase, distances from 7 through 27 days use weeks, and exact month/year multiples use the corrected unit count. |
| Symptom | Text changes without a compiler error. |
| Minimal action | Rebaseline display strings. If business logic depends on the text, replace that logic with date arithmetic before upgrading. |
| Verify | Cover equal dates, cross-year adjacent dates, 6/7 and 27/28 days, and 89/90/91 and 729/730/731 days in each used culture. |
| Evidence | Candidate same-date tests, precision-boundary tests, and the week-selection algorithm. |
Recheck month and year TimeSpan decomposition
When maxUnit is Month or Year and precision permits more units, the
remainder now includes weeks instead of expressing the entire remainder as
days.
| Call | 3.0.10 | Humanizer 4 candidate |
|---|---|---|
365 days, maxUnit: Year, precision 7 | 11 months, 30 days | 11 months, 4 weeks, 2 days |
60 days, maxUnit: Month, precision 7 | 1 month, 29 days | 1 month, 4 weeks, 1 day |
| Migration detail | What to do |
|---|---|
| Affected callers | TimeSpan.Humanize calls with maxUnit set to Month or Year, enough precision to include the remainder, and exact-output consumers. |
| Before | The remainder after approximate months or years is emitted in days. |
| After | Whole weeks are emitted before the remaining days. |
| Symptom | A week component appears in output that previously contained only months and days. |
| Minimal action | Rebaseline display text. Format the remainder in application code only when a days-only contract is required. |
| Verify | Cover spans around 30, 60, 365, and 366 days with the application's actual precision, maxUnit, and minUnit. |
| Evidence | Candidate month/year decomposition tests and the decomposition implementation. |
Treat metric decimals as a maximum
The decimals argument to ToMetric now sets the maximum number of
fractional digits. Trailing zeroes are omitted unless
MetricNumeralFormats.KeepTrailingZeros is supplied, and rounding can promote
the result to the next metric unit. Fixed-width precision must be from 0 through
15 when KeepTrailingZeros is supplied.
| Call | 3.0.10 | Humanizer 4 candidate |
|---|---|---|
123L.ToMetric(decimals: 3) | 123.000 | 123 |
999.9d.ToMetric(decimals: 0) | 1000 | 1k |
| Migration detail | What to do |
|---|---|
| Affected callers | Fixed-width metric labels, exact snapshots, and parsers that assume the previous trailing-zero or unit-boundary form. |
| Before | decimals fixes the number of fractional digits and boundary rounding can remain in the smaller unit. |
| After | decimals is a maximum; trailing zeroes are optional and a rounded boundary promotes the metric prefix. |
| Symptom | Trailing zeroes disappear or the metric prefix changes near a rounding boundary. |
| Minimal action | Add MetricNumeralFormats.KeepTrailingZeros for fixed-width display. Accept promoted units or apply an explicit application format when the unit itself is contractual. |
| Verify | Test integral values, fractional values, and values immediately below and above every used metric boundary. |
| Evidence | Candidate format flag and rounding tests. |
Recheck byte-size and byte-rate values
Existing byte APIs change at large unit and value-object boundaries:
| Call | 3.0.10 | Humanizer 4 candidate |
|---|---|---|
ByteSize.FromBytes(1_000_000_000_000_000).ToString() | 909.49 TB | 1 PB |
byteRate.ToString() for 400 bytes over 1 second | Humanizer.ByteRate | 400 B/s |
| Equality of 400 bytes/10 seconds and 800 bytes/20 seconds | unequal references | equal normalized rates and hash codes for the same runtime type; base/derived instances remain distinct |
Pick(byteRate) with Pick(object) and Pick(IComparable) overloads | selects Pick(object) | selects Pick(IComparable) |
Pick(byteRate) with Pick(object), Pick(IComparable), and Pick(IComparable<ByteRate>) overloads | selects Pick(object) | CS0121 ambiguous call between the sibling interface overloads |
| Migration detail | What to do |
|---|---|
| Affected callers | PB/EB-scale ByteSize display; code that logs, compares, hashes, or stores ByteRate values; and overload sets involving object, IComparable, IComparable<ByteRate>, or IEquatable<ByteRate>. |
| Before | Automatic ByteSize formatting stops at TB; ByteRate inherits object text and reference identity and implements none of those interfaces. |
| After | Automatic formatting selects PB/EB; same-runtime-type ByteRate instances compare by normalized rate, while base/derived comparisons remain type-sensitive. ByteRate implements comparison/equality interfaces. |
| Symptom | Large-size text changes, distinct same-runtime-type instances collapse to one value key while base/derived instances remain separate, or identical recompiled source selects a more specific interface overload or becomes ambiguous (CS0121) when sibling interface overloads coexist. |
| Minimal action | Request an explicit TB format when contractual. Use an identity comparer when reference identity is deliberate, keep runtime types consistent when value equality is intended, or cast explicitly to preserve old overload selection. |
| Verify | Cover TB/PB/EB boundaries, ByteRate.ToString, same-runtime-type and base/derived equivalent rates, dictionary/set behavior, and two- and three-overload sets after recompilation. |
| Evidence | Candidate large-unit tests, comparison/equality tests, and the ByteRate interface surface; focused stable/preview compile probes reproduce both object→IComparable selection and object→CS0121 ambiguity. |
Rebaseline classic English inflection corrections
The existing classic Pluralize, Singularize, and ToQuantity APIs correct
several English rules. This section does not describe the unreleased localized
inflection surface covered by #1892.
| Call | 3.0.10 | Humanizer 4 candidate |
|---|---|---|
"software".Pluralize() | softwares | software |
"PERSON".Pluralize() | People | PEOPLE |
"arrives".Singularize() | arrife | arrive |
"taxes".Singularize() | taxis | tax |
"specimen".Singularize() | speciman | specimen |
| Migration detail | What to do |
|---|---|
| Affected callers | Existing English inflection and quantity calls whose output is asserted, persisted, parsed, or used as a key. |
| Before | Some uncountable, all-caps, -ves, -axes, and unrelated -men inputs use incorrect generic rules. |
| After | The corrected vocabulary preserves uncountables and casing and distinguishes the affected endings. |
| Symptom | Exact English word forms change without a compile error. |
| Minimal action | Accept and rebaseline corrected display output, or register an application-specific vocabulary rule during startup when domain language requires another form. |
| Verify | Exercise the application's uncountables, all-caps words, -ves, -axes, and unrelated -men inputs through all three existing APIs. |
| Evidence | Candidate vocabulary rules and regression tests. |
Localization and culture changes
Ambient formatting now follows CurrentCulture
When a culture argument is omitted or null, registry-backed formatter and
converter paths now resolve from CultureInfo.CurrentCulture.
LocaliserRegistry.ResolveForUiCulture() remains explicitly tied to
CurrentUICulture. So do ToHeading() when its culture is omitted and
ToMetric(..., MetricNumeralFormats.UseScaleWord) when selecting a localized
scale word.
CultureInfo.CurrentCulture = CultureInfo.GetCultureInfo("en-US");
CultureInfo.CurrentUICulture = CultureInfo.GetCultureInfo("de-DE");
var words = 1.ToWords();
// 3.0.10 follows CurrentUICulture: "eins"
// Humanizer 4 follows CurrentCulture: "one"
| Migration detail | What to do |
|---|---|
| Affected callers | Registry-backed calls without an explicit culture in applications where formatting culture and UI resource culture differ. |
| Before | Default registries resolve formatting from CurrentUICulture. |
| After | Registry-backed formatting resolves from CurrentCulture; ResolveForUiCulture(), culture-omitting ToHeading(), and localized ToMetric scale words continue to use CurrentUICulture. |
| Symptom | Localized output changes language or number formatting even though the call site is unchanged. |
| Minimal action | Pass CultureInfo at the call site when output language is part of the contract. Otherwise align both ambient cultures at the request or job boundary. |
| Verify | Set the two ambient cultures to different values and exercise registry-backed numbers, ordinals, relative dates, durations, collections, and clock notation. Separately verify headings and localized metric scale words against CurrentUICulture. |
| Evidence | The candidate's registry resolution tests. |
Keep same-name custom cultures distinct
Formatter and converter registries now cache by CultureInfo instance instead
of culture name. Two custom en-US instances with different number or date
formats no longer reuse whichever formatter was resolved first.
| Migration detail | What to do |
|---|---|
| Affected callers | Applications that clone or construct multiple CultureInfo instances with the same name but different formatting properties, and code that deliberately primed the old registry cache. |
| Before | The first resolved culture instance wins for every later instance with the same culture name. |
| After | Each CultureInfo instance retains its own formatting identity. |
| Symptom | Output begins honoring the second instance's custom signs, separators, or date formatting instead of reusing the first instance's formatter. |
| Minimal action | Remove cache-priming workarounds. Reuse a single culture instance only when shared formatting is actually intended. |
| Verify | Resolve two same-name culture instances with visibly different number-format properties and confirm that each produces its own output. |
| Evidence | The candidate registry cache and same-name culture test. |
Recheck exact culture support
Configurator.IsCultureSupported and Humanizer's built-in registries now use
the generated accepted-culture inventory. They resolve only names that appear
in that inventory and do not infer support by walking CultureInfo.Parent.
An accepted regional name can still map to a neutral same-language profile.
Caller-created LocaliserRegistry<TLocaliser> instances retain their existing
parent-chain fallback.
| Migration detail | What to do |
|---|---|
| Affected callers | Applications that used Configurator.IsCultureSupported to accept any child of a supported neutral culture, or that relied on an unlisted parent fallback in a built-in registry. |
| Before | The support check and built-in registries walk the requested culture's named parent chain. |
| After | The exact culture name must exist in the generated accepted-culture inventory; an accepted name can map to a neutral profile without a runtime parent walk. |
| Symptom | IsCultureSupported returns false, or a built-in registry uses its default, for a previously unlisted child culture. |
| Minimal action | Check every culture name the application accepts. Use a listed accepted name, request an explicit mapping when the regional culture is valid, or choose the application's fallback deliberately. |
| Verify | Compare a known accepted regional name such as fr-FR with an unlisted child name, and confirm caller-created registries still follow their registered parent chain. |
| Evidence | The generated accepted-culture inventory, exact-name resolver, and accepted-culture regression tests. |
Rebaseline localized exact strings
Humanizer 4 replaces satellite-resource locale assemblies with generated locale
definitions and completes or corrects culture-specific behavior. These are
output changes, not a new package-reference requirement. For example,
new[] { "one", "two", "three" }.Humanize() under en-GB changes from the
fallback Oxford-comma form one, two, and three to the authored British form
one, two and three.
| Migration detail | What to do |
|---|---|
| Affected callers | Localized snapshots, persisted text, parsers that consume Humanizer output, and applications that relied on an English or parent-culture fallback. |
| Before | Locale satellite assemblies and resource-backed engines provide the 3.0.10 output set. |
| After | Generated locale profiles own formatting and converter behavior in the consolidated assembly. |
| Symptom | Corrected wording, punctuation, number scales, grammatical forms, or supported operations change exact output without a compile failure. |
| Minimal action | Inventory the cultures and operations the application actually uses, compare output side by side, and obtain language review before accepting persisted or user-facing changes. |
| Verify | For each used culture, cover relative dates, durations, collections, numbers and ordinals, parsing, data units, compass output, and clock notation as applicable. |
| Evidence | The candidate's exact locale-output matrix and British collection regression. |
The audited exact-output changes include these representative existing operations:
| Culture and affected callers | 3.0.10 | Humanizer 4 candidate | Verification and evidence |
|---|---|---|---|
en-GB collection humanization | one, two, and three | one, two and three | Check three-or-more-item collections. Regression |
pt and pt-BR number-to-words after a scale and exact hundred | quinze mil duzentos | quinze mil e duzentos | Check 15_200 and analogous scale boundaries. Regression |
pl durations, relative dates, and data units with compound paucal counts | 22 minut; za 22 minut; 22 bajtów | 22 minuty; za 22 minuty; 22 bajty | Check 12–14 and 21–25. Regression |
el feminine number-to-words | εννιακόσια; χίλια εννιακόσια | εννιακόσιες; χίλια εννιακόσιες | Check feminine 900 and 1900. Regression |
ru east/west intercardinal headings | forms such as востоко-северо-восток and западо-юго-запад | восток-северо-восток and запад-юго-запад | Check all four affected headings. Locale data |
cs, sk, and ro clock notation at minute one or two | pět jeden; five two; cinci doi | pět hodin jedna minuta; päť hodín dve minúty; ora cinci și două | Probe the exact cultures and minutes. 3.0.10 routes these cultures through the default converter; current regressions cover Czech, Slovak, and Romanian. Correction #1931 |
bs feminine ordinal words | English fallback second | Bosnian druga | Check several genders and compound values. V3 English fallback registry, focused v4 test, root PR #1829 |
hr, sl, sr, and sr-Latn feminine ordinal words | numeric fallback 2 | authored word druga (друга in Serbian Cyrillic) | V3 fallback implementations: Croatian, Slovenian, Serbian Cyrillic, and Serbian Latin. Focused v4 tests: Croatian, Slovenian, Serbian Cyrillic, and Serbian Latin. Root PR #1829 |
Do not infer localized-inflection behavior from the current candidate. The final #1892 API, resolver, generated data, and package diff must be audited and added here only after that issue is complete.
Analyzer-assisted changes
Humanizer 4 adds no new diagnostic ID for this migration. The packaged
words-to-number code fix attaches to compiler diagnostics CS0029 and CS0266
when the failing expression is a Humanizer ToNumber call or an
IWordsToNumberConverter.Convert call. In an IDE, choose Wrap with checked
int cast only where the application contract must remain 32-bit.
HUMANIZER001 still finds Humanizer 2 namespace usage. A clean 3.0.10
consumer should not need it for the Humanizer 4 migration, and it does not fix
package references, removed resource APIs, custom converter signatures, or
behavioral output changes.
Verify the analyzer package itself by confirming that the selected build host
loads one Humanizer.Analyzers.dll and reports neither CS8032 nor AD0001.
The analyzer guide covers build-host diagnostics and
severity configuration.
Validation checklist
Run these checks after source changes, then repeat them on the final Humanizer 4 tag:
dotnet clean <solution>
dotnet restore <solution> --force-evaluate
dotnet list <project> package --include-transitive
dotnet build <solution>
dotnet test <solution>
- The package list contains one Humanizer version and no
Humanizer.Core*. - Every target framework and CI build host restores and compiles.
- Custom
IWordsToNumberConverterimplementations exposelongmembers. - Any intentional 32-bit narrowing is checked and boundary-tested.
- No custom formatter overrides the removed
DefaultFormatterresource-key hooks. - Analyzer loading emits no
CS8032orAD0001. - Identifier, title, enum, date, duration, metric, byte, and classic-inflection output was reviewed.
ByteRateequality, hashing, implemented interfaces, and overload selection were reviewed.CurrentCultureandCurrentUICulturewere tested separately.- Same-name custom
CultureInfoinstances were tested separately when used. - Every used culture and applicable localized operation has reviewed output.
:::note Incidental resource implementation surface
Resources and ResourceKeys were public resource plumbing, not an intended
application contract. Humanizer 4 removes those types and the old manifest and
satellite resources. For completeness, direct callers or reflection can get
CS0103, CS0246, or TypeLoadException; replace them with the typed operation
that owns the output, such as TimeUnit.ToSymbol. See the
3.0.10 resource metadata.
:::