ASP.NET Core provides the new type of IStringLocalizer<T>
to deal with string resources
and localization. There has been written a lot about this already and a simple search
will provide you with all background information you need - so I’ll skip this here, only
talking about the basic bits where they are required to what is done and why.
Usually, you would use a IStringLocalizer<T>
where T
is a type (say MyService
)
and then also provide a .resx
file of the same name, including the culture name
for the translations (e.g. MyService.de-DE.resx
). Note that you specifically, don’t
need to provide a resource file for the default language of your application.
Let’s look at the following example to make this more clear:
In the above you can see a couple of things:
IStringLocalizer<MyService>
is usually provided by DI.For the following, there is one important thing to keep in mind:
You cannot create instances of IStringLocalizer<T>
directly;
you are expected to have them injected.
That doesn’t sound too bad, but consider the following example:
So you have an helper method (static to make the case obvious that DI is possible) that is used by multiple other services or code.
Where would you put the resources for this? You could put them
into IStringLocalizer<SharedResources>
, but simply defining
an empty class called SharedResources
and SharedResources.<culture>.resx
that goes with it.
However, that doesn’t scale too well in bigger projects:
IStringLocalizer<T>
into other classesIStringLocalizer<T>
you need to inject to
satisfy all code that a service may call downstream.Especially the last point breaks encapsulation and is plain cumbersome to maintain.
For example, consider this:
A possible solution to this is to only inject the on IStringLocalizer<T>
into a service, the one that is directly “bound” to it. But then, have
that localizer name dependencies that should also be available.
For exempale:
OK, you still have to note all the dependencies here, but at least not on the constructor directly. Additionally, if you define a proper resource hierarchy for your project you can make things a little more obvious.
For example, in the project where I employed this technique basically all code was structured like this:
Each level could have resources so that by default every controller
resource would only have InheritStringLocalization
-attributes for
the services it uses, and every service resource would only have them
for the utility classes it uses. In other words, each level only has
explicit dependencies to the immediate next level. Something which
is not possible if you use the plain DI-approach that the framework
provides.
The InheritStringLocalization
-attribute basically says that when
attempting to resolve a resource from the type it is applied on, the
type specified by the attribute should also be consider, if the
resource is not found in the type itself. It does that down the
chain. So if the type specified by the attribute does not contain
the resource, but has an InheritStringLocalization
-attribute itself,
this type is searched, and so on.
If multiple attributes are applied to a resource type, the first that
contains the resource is used. The search order can be influenced by
an optional Priority
-parameter for the attribute:
Lower priority values are considered first.
Finally note, that using this approach one can create a resource type that serves merely as a bundle, but does not define any resources itself:
This is the attribute that is applied to resource types to indicate that they should “inherit” resources from the specified “InheritFrom” type.
This is the implementation behind an IStringLocalizer<T>
that uses a T
that has at least
InheritStringLocalizationAttribute
applied.
The implementation is rather straight forward. Simply iterate all passed localizers until one returns the resource in question.
This class is responsible for actually creating our MultiStringLocalizer
types.
It needs to be registered with DI, which can be done with code like this:
The actual factory implementation looks like this: