LocalizationModule¶
The LocalizationModule
provides basic features to work with localized
resources for the cultures supported by the application under development.
The LocalizationModule
sits on top of the features provided by .NET, this
means that all the standard localization features can still be used to build up
your applications.
Note
By now the LocalizationModule
manages the localization resources related
to backend tier, but in the future either backend or frontend resources
will be managed by this module.
Localization concepts and components¶
The LocalizationModule is organized in Bundles
, each bundle contains a set of
resources related to a feature or module, for all the supported cultures. You
can think of a bundle as a key-value
dictionary of related resources required
by your module.
Each localization Bundle
is represented by an instance of
ILocalizationResource
, which in turns can be assigned a set of
ILocalizationResourceContributor
to contribute filling out the dictionary.
Each Bundle
has attributes to define its characteristics, content and
behavior:
-
An identifier: it is a
Type
used to uniquely identify the resource bundle. You will need to use this type identifier to refer to the resource bundle whenever you need to localize a resource out of it. TheBundle
type identifier is accessible through theResourceType
attribute . -
A contributor list: contributors fills out the resource bundle from a variety of sources, such as text files, databases, remote calls, etc. The contributor list is accessible through
Contributors
attribute of theILocalizationResource
instance. -
A default culture: default culture will be used when no other culture matches the search criteria to localize a resource. The culture is set through the
DefaultCultureName
attribute of theILocalizationResource
instance. -
Base resource collection: base resources act as parent localization source from which the resource bundle being defined is extending and / or overwriting preexistent resources. The base resource type collection is accessible through the
BaseResourceTypes
attribute of theILocalizationResource
instance.
Important
Every single message, text, label, etc., needed to be shown, sent, or
displayed to the end user in your application must be done aware of the
current culture. Thus, it must be obtained out of the proper
IStringLocalizer
instance.
Registering localization bundles¶
Each module must register its own bundle in the Resources
collection of the
LocalizationModuleOptions
object provided by the Suite Framework, to be able
to localize resources later on.
The following example shows the way to add and configure a new resource bundle:
Some things to note from the example shown earlier:
-
The configuration is made by providing an action with the desired code block in
SetupModule
method of yourISuiteModule
implementation. -
The
Add
method available inLocalizationModuleOptions
instance is requires a type parameter that acts as the module identifier. -
In the example the
AddJsonEmbeddedDirectory
,AddJsonDirectory
,AddJsonFile
, andAddJsonEmbeddedFile
methods add aJsonFileLocalizationContributor
instance to fill out the localization dictionary. See here for further information on usingJson
files for localization. -
Several contributors were added to the bundle being configured, each of one providing a culture specific localization resource set.
Note
Typically there will be one Bundle
for each Suite Module
, more instances
are seldom needed, we will assume the case where only one bundle exists for
each module for the rest of the document.
Localizing resources¶
Any time you want to provide a localized message you will need to obtain an
IStringLocalizer
instance and get the LocalizedString
containing the
localized resource for a given culture out of it.
IStringLocalizer
instances are created by the IStringLocalizerFactory
component. The default .Net Core Framework
factory is replaced during
bootstrapping with the one provided by the `Suite Framework to provide an
enhanced version with the needed behavior to support our use cases.
The next example illustrates how to get an instance of IStringLocalizer
injecting it in the constructor of an IAppService
. This mechanism can be used
to inject the desired IStringLocalizer
instance out of DI in any other
component or feature, such as controllers and services, for instance.
Some things to note from previous example:
-
The
IStringLocalizer
instance is injected by DI and referring to theJobsAppModuleResources
resource bundle using its type parameter. -
The
IStringLocalizer<T>
instance can be assigned to a more genericType
attribute, such asIStringLocalizer
, for clarity and simplicity of code. -
The
IStringLocalizer
instance is used to retrieve the culture aware resource identified byMSG_GREETING
andMSG_GREETING_WITH_TIME
names. -
A given resource can be used to show plain texts or as templates whose placeholders are filled in with provided params at runtime (one or more parameters can be provided at once).
The localization will be performed using the CurrentUICulture
of the current
Thread
. Please continue reading next sections for further information about
this topic.
Note
An intent to localize a resource not contained in a Bundle
will produce a
non empty result always. Every localization operation ends up with an instance
of LocalizedString, even if no resource can be found with the given key. A
default response will be provided in such cases, and if you have to take care
about the successful or not, you will then need to inspect the
ResourceNotFound
property of the result.
A more complex scenario might be supported by injecting multiple instances of
IStringLocalizer
pointing to a different Bundle
each, or even injection
IStringLocalizationFactory
, which allows us to get instances of the desired
IStringLocalizer
for specific bundles. Let's see in the following examples.
Using multiple typed localizer instances:
Using the localizer factory:
Determining the CurrentUICulture¶
The Localization
module depends on the Localization middleware
features
provided by the ASP.Net Core Framework
.
The Suite Framework relays in the request culture providers of the
ASP.Net Core Localization Middleware
. The way the framework assign defines
which is the target culture is explained
here
DefaultCulture
and SupportedCultures
assigned to the
Suite Framework's LocalizationModule
attributes at bootstrap time are used
later to configure the underlying ASP.Net Core Localization Middleware
. The
request culture providers provided by the framework, out of the box, are then
configured and used as well to define the current culture. The order in which
the provides performs to determine the CurrentUICulture
is as follows:
-
QueryStringRequestCultureProvider: uses the
QueryString
parameters to determine the request culture. -
CookieRequestCultureProvider gets the current culture out of a cookie.
-
AcceptLanguageHeaderRequestCultureProvider uses a well-known header value to determine the request culture.
LocalizationModule options¶
The LocalizationModule provides the following options to configure the feature and its behavior:
Resources
: the collection of resource bundles for the application.DefaultCulture
: the default culture which is used by the application when a supported culture could not be determined by one of the configuredMicrosoft.AspNetCore.Localization.IRequestCultureProviders
. By default the Suite Framework sets theen
(english neutral) culture name.SupportedCultures
: the set of the supported cultures by the application.
The SupportedCultures
and DefaultCulture
play an important role when the
current culture must be determined for a given incoming request, in conjunction
with IRequestCultureProviders
.
DefaultCulture
and SupportedCultures
values are used to configure the said
and underlying ASP.Net Core Localization Middleware through
RequestLocalizationOptions
. For more information please refer to the
Official .NET Documentation
Culture determination procedure (fallback)¶
Any time a Localized
resource is requested for a Bundle
, the following
procedure is followed (the first not null outcome is returned):
- Contributors for the
Bundle
are invoked in the defined order, using the current culture. - If not results were found with current culture, and if a parent culture exists for the current culture, the contributors will be invoked again for the parent culture.
- If we haven't succeeded yet finding the resource, if a
DefaultCultureName
was defined for theBundle
, the contributors will be invoked again for theBundle
's default culture. - Finally base resource localizer instances, if any, will be invoked in its defined order, using the current culture initially, and culture fallback procedure mentioned here previously, if needed, afterwards.
Seamlessly integration with Resource (.resx) files¶
The LocalizationModule
integrates seamlessly with .resx Resource
files, such
files can still be used for translation.
The recommended way to define Bundles
when working with the Suite Framework is
using LocalizationResourceDictionary
, because this way the you will be able to
extend from an existent Bundle
, overriding existing, or adding new resources
to it.
Any time a localized resource is requested, and the Bundle
is not known by the
LocalizationModule
, the localization operation will be delegated to the
underlying ResourceManagerStringLocalizerFactory
, which will try to handle the
localization procedure using Resource (.resx) files
.
The ResourceManagerStringLocalizerFactory
factory receives all the
localization requests that cannot be handled using SuiteLocalizerFactory
.
Note
When using resource files (.resx), the base is set by default to Resources
.
Take this into account because it makes the difference at the moment of deciding
where to place the resource file (and its locale specific satellite assembly
siblings for each supported culture). See here
for more information about naming resource (.resx) files.
Using base types¶
Each Bundle
can be assigned one or more Base types
in order to inherit and
extends, or even overwrite, the resources defined by base Bundles
. In order to
point to a base Bundle
its identifier type must be used on the Bundle
being
extending it. Lets see an example:
In the above example the Type
identifying SomeBaseResource Bundle
was used
to indicate that MyModuleResources
Bundle
will use, extend or overwrite
resources coming from SomeBaseResource Bundle
. Think of this inheritance as a
class hierarchy where ancestors provide resources than can be used by
descendants. The inheritance is followed all along the hierarchy, thus if
SomeBaseResource
in turn has some inheritance definition, those localization
resources will be available also for the MyModuleResources Bundle
.
The next code snippet continues extends the previous example showing how the
Bundle
registration is done in a more complete code snippet.
Note
Take care of ancestors being registered as Bundles
or it will produce a race
condition when trying to configure and use the LocalizationModule
, because of
the reference made to the missing base Bundle
.
Remember that you can overwrite partially or none at all the inherited localization resources defined by base resources. Therefore, the following scenarios are valid, and its combination also:
- Extend base resources with new ones, if you wan to extend previous definitions only.
- Replace whole specific culture, if you want to replace the definitions only for an idiom or locale for example.
- Replace some specific resource, if you need to do some sort of branding.
Using Json
files for localization¶
When defining localized resources, a Json
file can be used. Each Json
file
must provide resources only for one culture.
Note
By now, the only available contributor is JsonFileLocalizationContributor
,
which allows us to feed the Bundle
instance from Json
files. We expect to
add more contributors in the feature.
Keep in mind that Json
files will be read at runtime, therefore any kind of
race condition will not be seen until the application is running.
Warning
Avoid to provide more than one file for the same culture, because that will produce an exception at runtime.
Json
file structure¶
The structure of the Json
file must follow a set of directives to be
considered Well Formed
and its content can be read by the framework. Below you
can see an example of a valid Json
localization resource file.
JSON | |
---|---|
In the file there must be defined a culture
attribute, and texts
attribute
to hold the collection of key/value
pairs holding the localization resource
name and value respectively.
Loading resources from assembly embedded files¶
To use json files embedded as assembly resources, you need to point to the
correct embedded resource name. As embedded resources don't have a "directory"
concept, AddJsonEmbeddedDirectory()
will actually add all resources that start
with the specified directory path, so you need to be careful to not add
subdirectories by mistake. Also, as embedded resources don't allow directory
separator characters in their names, the "/" and "\" characters will be mapped
to the "." character, which is the way they are converted when embedded.
Note
Using embedded resources is preferred over using filesystem files, as this last option has caused conflicts in the past when different modules tried to add files to the "\resources" directory.
You need to modify your project's .csproj file to embed files in the assembly manifest. For example, to add all json files in the Resources directory to the assembly manifest, you should add this to your project's .csproj file:
XML | |
---|---|
Warning
Please be careful configuring the build action correctly so that the embedded resources are added to the assembly.
Note
The culture name suffix present in the file name of the previous example is
merely for illustration and clarity purpose, those suffixes will be ignored
because the target culture is defined by the culture
attribute
inside each Json
file.
Warning
Avoid using embedded resources that don't contain Json
localization
files only, because it can produce unwanted exceptions at runtime. Use this
feature with caution.
Loading resources from files or directories¶
Json
file contributors can be added through suitable extensions methods
provided by the Suite framework
. To use it you need to point to a valid Json
path or directory. If the given path is not fully qualified, the path will be
considered relative to the current executing assembly, and the resulting path
will be the combination of both.
Warning
Please be careful configuring the build action correctly for all the Json
files you want to use, because they must be present a runtime in the path you
pointed at when adding to the Bundle
.
C# | |
---|---|
Note
The culture name suffix present in the file name of the previous example is
merely for illustration and clarity purpose, those suffixes will be ignored
because the target culture is defined by the culture
attribute
inside each Json
file.
In the previous example the resources will be loaded at runtime from the provided path relative to the executing assembly location:
Text Only | |
---|---|
and
Text Only | |
---|---|
respectively.
Warning
Avoid pointing to a directory whose content doesn't contain Json
localization
files only, because it can produce unwanted exceptions at runtime. Use this
feature with caution.
Localization for UI applications¶
The AspNetLocalizationModule
along with LocalizationModule
provide the
features needed to localize UI applications leveraging the Suite's localization
features.
To do so, have to create a module that depends on LocalizationModule
and
AspNetLocalizationModule
altogether. Said module holds all the localizable
resources required by the UI application to be exposed to the UI, which will be
exposed through an http api, using a well known path.
The steps to follow are these:
- Create the UI localization module, you can leverage the
suiteuilocalizationmodule
template. - Make your module depend on the
LocalizationModule
, adding your resource bundle to its options. - Make your module depend on the
AspNetLocalizationModule
, exposing the bundle added in the previous step. - Add your UI resources to the module, for all supported languages.
- Make your
Application
orBFF
depend on the UI localization module. Add other UI localization modules, if needed.
Note
Most of this burden is done by the template, we encourage you to use it for bootstrapping the development process.
Step 1 to 3 are shown in the example here below.
Naming convention¶
Given the fact that it's very likely to have to tweak the localization resources after build, perhaps because of branding, minor adjustments, or even to fix misspelled phrases or terms, we must be clear when naming the localization resources, to be able to distinguish clearly and decide correctly which one to change among all the ones contained in the resource bundle.
We encourage you to use this convention, it provides guidelines to name localization resource assets, giving the reader a decent hint, or context, on what the purpose of the resource is, and where is it supposed to be used, just by reading its key. After all, we don't have anything else but key to provide context for the resource being described.
The following are proposed rules for naming resources:
- LBL_: use this prefix when the resource is going to be used to label an item which is might be bounded in size, such as menu items, tab titles, etc. LBL_ADMIN_ASSETS_HEADER
- BTN_: same meaning as LBL_ but specifically used to denote an
action rather than a noun, typically used to label buttons or links. For
example:
BTN_SAVE
, orBTN_SAVE_CURRENT_ASSET
. - MSG_: use this when the resource is used as text without sizing
boundaries, such as text blocks or text paragraph in applications, being
harmless if its size may vary when localized. For example:
MSG_INVALID_USER_MESSAGE
Some things to note about this convention:
- The names are in UpperSnakeCase
- The suffix identify the purpose or context where the resource is used
- The localized text can be either plain texts or templates
Keep in mind that in the future these definitions can vary to hold more scenarios or to fit some new features.
Note
There is no need to provide application id suffix or prefix, because the framework will do it for you, isolating resources located in each bundle. Keep the resource names simple, but yet meaningful.
Localized resources are limited by now to texts, no multimedia nor binary resources are supported today. If you wan to provide localization for those scenarios, you can use identifiers pointing to external assets (files, videos, blob storage, etc/) valid for the target culture.
Using localized templates¶
Localized resources can contain placeholders, which will be later filled in with provided parameters at runtime. The valid format and placeholders are those defined by String.Format
Info
The way we provide formatting for can be changed in the future, when adding
localization support for client applications that can be written in other
languages and technologies other than C#
.
Warning
When using templates avoid composing phrases combining several terms in your application, the reason is that for some languages the term position can vary, and the phrase can completely change its sense (or even make no sense at all) depending on the target language.