Humanizr/Humanizer · error · InvalidOperationException
Cannot register localisers after the registry has been used.
Error message
Cannot register localisers after the registry has been used.
What it means
Thrown by LocaliserRegistry<T>.Register(string, TLocaliser) when you attempt to register a localiser instance after the registry has already been 'frozen'. Freezing happens lazily on the first ResolveForCulture/ResolveForUiCulture call, which converts the builder dictionary into a FrozenDictionary for fast reads. The registry is immutable after first use; this InvalidOperationException enforces that contract.
Source
Thrown at src/Humanizer/Configuration/LocaliserRegistry.cs:58
public TLocaliser ResolveForCulture(CultureInfo? culture)
{
var cultureInfo = culture ?? CultureInfo.CurrentCulture;
return cultureSpecificCache.GetValue(
cultureInfo,
c => new(FindLocaliser(c)(c))
).Value!;
}
/// <summary>
/// Registers the localiser for the culture provided
/// </summary>
public void Register(string localeCode, TLocaliser localiser)
{
lock (lockObject)
{
if (frozenLocalisers != null)
{
throw new InvalidOperationException("Cannot register localisers after the registry has been used.");
}
localisersBuilder[localeCode] = _ => localiser;
}
}
/// <summary>
/// Registers the localiser factory for the culture provided
/// </summary>
public void Register(string localeCode, Func<CultureInfo, TLocaliser> localiser)
{
lock (lockObject)
{
if (frozenLocalisers != null)
{
throw new InvalidOperationException("Cannot register localisers after the registry has been used.");
}
localisersBuilder[localeCode] = localiser;
}View on GitHub (pinned to ffc2b77c0f)
Solutions
- Move all Register calls into a single early startup path (Program.Main / a bootstrap extension) before any Humanizer formatting method is invoked.
- If you must change localisers per request/test, do not use the shared static registries; construct a standalone LocaliserRegistry<T> instance instead.
- Detect the ordering problem by searching for the first ToWords/Humanize/ResolveForCulture call and ensuring all Register calls precede it.
Example fix
// before (bug): first request humanizes, then a module registers late
app.MapGet("/x", () => 1.ToWords());
// later, in a hosted service:
Humanizer.Configuration.Configurer.Formatters.Register("fr", myFormatter); // throws
// after: register during startup, before serving requests
Humanizer.Configuration.Configurer.Formatters.Register("fr", myFormatter);
var app = builder.Build();
app.MapGet("/x", () => 1.ToWords()); Defensive patterns
Strategy: validation
Validate before calling
// Ensure all Register calls happen before the first Humanizer resolve call. // There is no public API to check the frozen state; enforce ordering structurally // by registering only in your composition root, before serving any request.
Try / catch
try { Configurer.Formatters.Register("fr", myFormatter); }
catch (InvalidOperationException)
{ /* registry already in use: move registration earlier or use a private registry */ } Prevention
- Register all localisers in a single composition-root phase before any formatting call.
- Avoid per-request/per-test registration on the shared static registries.
- For test isolation, instantiate your own LocaliserRegistry<T> rather than mutating global state.
When it happens
Trigger: Calling any Humanizer API that resolves a localiser (e.g. number.ToWords(), TimeSpan.Humanize()) and then later calling something like Configurer.Formatters.Register(...) or another LocaliserRegistry.Register. The first resolve freezes the internal dictionary, after which Register throws.
Common situations: App startup order bugs: a module registers a custom formatter in a delayed initializer or middleware that runs after the first request has already humanized something; testing code that registers per-test localisers but shares a static registry between tests; ASP.NET hosted services that resolve Humanizer during startup and then try to register in a later IHostedService.
Related errors
- A property path is required.
- Billion-word cardinal strategy requires a singular billion w
- Billion-word cardinal strategy requires a plural billion wor
- Property '{propertyName}' must be a string, array, or mappin
- Property '{propertyName}' must be a mapping.
AI-assisted analysis of Humanizr/Humanizer@ffc2b77c0f (2026-08-13).
Data as JSON: /api/errors/33eca4da61d39a0b.
Report an issue: GitHub.