{"record":{"id":"33eca4da61d39a0b","repo":"Humanizr/Humanizer","slug":"cannot-register-localisers-after-the-registry-has","errorCode":null,"errorMessage":"Cannot register localisers after the registry has been used.","messagePattern":"Cannot register localisers after the registry has been used\\.","errorType":"exception","errorClass":"InvalidOperationException","httpStatus":null,"severity":"error","filePath":"src/Humanizer/Configuration/LocaliserRegistry.cs","lineNumber":58,"sourceCode":"    public TLocaliser ResolveForCulture(CultureInfo? culture)\n    {\n        var cultureInfo = culture ?? CultureInfo.CurrentCulture;\n        return cultureSpecificCache.GetValue(\n            cultureInfo,\n            c => new(FindLocaliser(c)(c))\n        ).Value!;\n    }\n\n    /// <summary>\n    /// Registers the localiser for the culture provided\n    /// </summary>\n    public void Register(string localeCode, TLocaliser localiser)\n    {\n        lock (lockObject)\n        {\n            if (frozenLocalisers != null)\n            {\n                throw new InvalidOperationException(\"Cannot register localisers after the registry has been used.\");\n            }\n            localisersBuilder[localeCode] = _ => localiser;\n        }\n    }\n\n    /// <summary>\n    /// Registers the localiser factory for the culture provided\n    /// </summary>\n    public void Register(string localeCode, Func<CultureInfo, TLocaliser> localiser)\n    {\n        lock (lockObject)\n        {\n            if (frozenLocalisers != null)\n            {\n                throw new InvalidOperationException(\"Cannot register localisers after the registry has been used.\");\n            }\n            localisersBuilder[localeCode] = localiser;\n        }","sourceCodeStart":40,"sourceCodeEnd":76,"githubUrl":"https://github.com/Humanizr/Humanizer/blob/ffc2b77c0f30d2fb176875841424379319d0ae9b/src/Humanizer/Configuration/LocaliserRegistry.cs#L40-L76","documentation":"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.","triggerScenarios":"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.","commonSituations":"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.","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."],"exampleFix":"// before (bug): first request humanizes, then a module registers late\napp.MapGet(\"/x\", () => 1.ToWords());\n// later, in a hosted service:\nHumanizer.Configuration.Configurer.Formatters.Register(\"fr\", myFormatter); // throws\n\n// after: register during startup, before serving requests\nHumanizer.Configuration.Configurer.Formatters.Register(\"fr\", myFormatter);\nvar app = builder.Build();\napp.MapGet(\"/x\", () => 1.ToWords());","handlingStrategy":"validation","validationCode":"// Ensure all Register calls happen before the first Humanizer resolve call.\n// There is no public API to check the frozen state; enforce ordering structurally\n// by registering only in your composition root, before serving any request.","typeGuard":null,"tryCatchPattern":"try { Configurer.Formatters.Register(\"fr\", myFormatter); }\ncatch (InvalidOperationException)\n{ /* registry already in use: move registration earlier or use a private registry */ }","preventionTips":["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."],"tags":["configuration","registry","lifecycle","threading","invalid-operation"],"backgroundTag":null,"analyzedSha":"ffc2b77c0f30d2fb176875841424379319d0ae9b","analyzedAt":"2026-08-13T21:42:34.584Z","schemaVersion":2},"datasetVersion":"2026-08-14T00:17:13.853Z"}