What you are seeing is two universes collide; both DI libraries have their own, incompatible, definition of what it means to be transient:
- Simple Injector considers components with a
Transient lifestyle to be short lived, or brief. That’s why Simple Injector doesn’t allow transients to be injected into scoped components; a scope could live for a much longer time than would be considered brief.
- ASP.NET Core does not consider transient components to be short lived at all. Instead, the expected lifetime of a
Transient component in .NET Core is to be as long as its consumer's intended lifetime. The docs even state that "This lifetime works best for lightweight, stateless services."
UPDATE With Simple Injector v5, this behavior has been loosened up a bit, where it is now (by default) okay to inject transients into scoped components. The definition of what transient means, however, stays unchanged, meaning that Simple Injector still considers it an error when you try to inject a transient into a singleton.
It is because of this behavior that the Microsoft.Extensions.DependencyInjection (MS.DI) container allows injecting transients into both singleton and scoped consumers.
I would even argue that Microsoft named their lifestyle incorrectly, because the actual behavior is to have one instance per consumer's dependency. Microsoft seems to have copied this behavior from Autofac. Autofac, however, does name this same lifestyle InstancePerDependency, which is a much more obvious name, if you ask me.
Weird thing, though, is that the Microsoft's AddLocalization extension method registers StringLocalizer<T> as transient. This is weird, because, apart from the wrapped IStringLocalizer, StringLocalizer<T> doesn't have any state. And not only that, the IStringLocalizer that it wraps is produced by the injected IStringLocalizerFactory and can be expected to be the same instance (which is enforced by the fact that the ResourceManagerStringLocalizerFactory caches returned instances).
As stated above, within MS.DI, Transient means “I'll live as long as my consumer does. “ This practically means that a StringLocalizer<T> instance can live as long as a singleton, meaning: for the duration of the entire application.
From this respect it is actually really weird that the localization team chose StringLocalizer<T> to have a transient lifestyle, even in MS.DI. Transient only means more instances created, and the IStringLocalizerFactory invoked more often than required. I find Singleton a much more obvious lifestyle for that registration.
Long story short, I would suggest overriding the default registration with a singleton, as it is safe to do so anyway:
services.AddLocalization();
services.AddSingleton(typeof(IStringLocalizer<>), typeof(StringLocalizer<>));