Localization is the process of translating an application's resources into localized versions for each culture that the application will support. You should proceed to the localization step only after completing the Localizability review step to verify that the globalized application is ready for localization.
An application that is ready for localization is separated into two conceptual blocks: a block that contains all user interface elements and a block that contains executable code. The user interface block contains only localizable user-interface elements such as strings, error messages, dialog boxes, menus, embedded object resources, and so on for the neutral culture. The code block contains only the application code to be used by all supported cultures. The common language runtime supports a satellite assembly resource model that separates an application's executable code from its resources. For more information about implementing this model, see Resources in .NET.
For each localized version of your application, add a new satellite assembly that contains the localized user interface block translated into the appropriate language for the target culture. The code block for all cultures should remain the same. The combination of a localized version of the user interface block with the code block produces a localized version of your application.
The primary mechanism for isolating localizable strings is with resource files. A resource file is an XML file with the .resx file extension. Resource files are translated prior to the execution of the consuming application—in other words, they represent translated content at rest. A resource file name most commonly contains a locale identifier, and takes on the following form:
<FullTypeName><.Locale>.resx
Where:
The <FullTypeName> represents localizable resources for a specific type.
The optional <.Locale> represents the locale of the resource file contents.
Specifying locales
The locale should define the language, at a bare minimum, but it can also define the culture (regional language), and even the country or region. These segments are commonly delimited by the - character. With the added specificity of a culture, the "culture fallback" rules are applied where best matches are prioritized. The locale should map to a well-known language tag. For more information, see CultureInfo.Name.
Culture fallback scenarios
Imagine that your localized app supports various Serbian locales, and has the following resource files for its MessageService:
File
Regional language
Country Code
MessageService.sr-Cyrl-RS.resx
(Cyrillic, Serbia)
RS
MessageService.sr-Cyrl.resx
Cyrillic
MessageService.sr-Latn-BA.resx
(Latin, Bosnia & Herzegovina)
BA
MessageService.sr-Latn-ME.resx
(Latin, Montenegro)
ME
MessageService.sr-Latn-RS.resx
(Latin, Serbia)
RS
MessageService.sr-Latn.resx
Latin
MessageService.sr.resx
† Latin
MessageService.resx
† The default regional language for the language.
When your app is running with the CultureInfo.CurrentCulture set to a culture of "sr-Cyrl-RS" localization attempts to resolve files in the following order:
MessageService.sr-Cyrl-RS.resx
MessageService.sr-Cyrl.resx
MessageService.sr.resx
MessageService.resx
However, if your app was running with the CultureInfo.CurrentCulture set to a culture of "sr-Latn-BA" localization attempts to resolve files in the following order:
MessageService.sr-Latn-BA.resx
MessageService.sr-Latn.resx
MessageService.sr.resx
MessageService.resx
The "culture fallback" rule will ignore locales when there are no corresponding matches, meaning resource file number four is selected if it's unable to find a match. If the culture was set to "fr-FR", localization would end up falling to the MessageService.resx file which can be problematic. For more information, see The resource fallback process.
Resource lookup
Resource files are automatically resolved as part of a lookup routine. If your project file name is different than the root namespace of your project, the assembly name might differ. This can prevent resource lookup from being otherwise successful. To address this mismatch, use the RootNamespaceAttribute to provide a hint to the localization services. When provided, it is used during resource lookup.
The example project is named example.csproj, which creates an example.dll and example.exe—however, the Localization.Example namespace is used. Apply an assembly level attribute to correct this mismatch:
[assembly: RootNamespace("Localization.Example")]
Register localization services
To register localization services, call one of the AddLocalization extension methods during the configuration of services. This will enable dependency injection (DI) of the following types:
Resource files can live anywhere in a project, but there are common practices in place that have proven to be successful. More often than not, the path of least resistance is followed. The preceding C# code:
The IStringLocalizer also supports parameterized string resources, consider the following ParameterizedMessageService:
using System.Diagnostics.CodeAnalysis;
using Microsoft.Extensions.Localization;
namespace Localization.Example;
public class ParameterizedMessageService(IStringLocalizerFactory factory)
{
private readonly IStringLocalizer _localizer =
factory.Create(typeof(ParameterizedMessageService));
[return: NotNullIfNotNull(nameof(_localizer))]
public string? GetFormattedMessage(DateTime dateTime, double dinnerPrice)
{
LocalizedString localizedString = _localizer["DinnerPriceFormat", dateTime, dinnerPrice];
return localizedString;
}
}
In the preceding C# code:
A IStringLocalizer _localizer field is declared.
The primary constructor takes an IStringLocalizerFactory parameter, which is used to create an IStringLocalizer from the ParameterizedMessageService type, and assigns it to the _localizer field.
The GetFormattedMessage method invokes IStringLocalizer.Item[String, Object[]], passing "DinnerPriceFormat", a dateTime object, and dinnerPrice as arguments.
هام
The IStringLocalizerFactory isn't required. Instead, it is preferred for consuming services to require the IStringLocalizer<T>.
The localization services, MessageService, and ParameterizedMessageService are registered to the IServiceCollection for DI.
To remove noise, logging is configured to ignore any log level lower than a warning.
The MessageService is resolved from the IServiceProvider instance and its resulting message is logged.
The ParameterizedMessageService is resolved from the IServiceProvider instance and its resulting formatted message is logged.
Each of the *MessageService classes defines a set of .resx files, each with a single entry. Here is the example content for the MessageService resource files, starting with MessageService.resx:
<?xml version="1.0" encoding="utf-8"?>
<root>
<data name="GreetingMessage" xml:space="preserve">
<value>Hi friends, the ".NET" developer community is excited to see you here!</value>
</data>
</root>
MessageService.sr-Cyrl-RS.resx:
<?xml version="1.0" encoding="utf-8"?>
<root>
<data name="GreetingMessage" xml:space="preserve">
<value>Здраво пријатељи, ".NЕТ" девелопер заједница је узбуђена што вас види овде!</value>
</data>
</root>
MessageService.sr-Latn.resx:
<?xml version="1.0" encoding="utf-8"?>
<root>
<data name="GreetingMessage" xml:space="preserve">
<value>Zdravo prijatelji, ".NET" developer zajednica je uzbuđena što vas vidi ovde!</value>
</data>
</root>
Here is the example content for the ParameterizedMessageService resource files, starting with ParameterizedMessageService.resx:
<?xml version="1.0" encoding="utf-8"?>
<root>
<data name="DinnerPriceFormat" xml:space="preserve">
<value>U {0:D} moja večera je koštala {1:C}.</value>
</data>
</root>
تلميح
All of the resource file XML comments, schema, and <resheader> elements are intentionally omitted for brevity.
Example runs
The following example runs show the various localized outputs, given targeted locales.
Consider "sr-Latn":
dotnet run --project .\example\example.csproj sr-Latn
warn: Localization.Example[0]
Zdravo prijatelji, ".NET" developer zajednica je uzbuđena što vas vidi ovde!
warn: Localization.Example[0]
U utorak, 03. avgust 2021. moja večera je koštala 37,63 ¤.
When omitting an argument to the .NET CLI to run the project, the default system culture is used—in this case "en-US":
dotnet run --project .\example\example.csproj
warn: Localization.Example[0]
Hi friends, the ".NET" developer community is excited to see you here!
warn: Localization.Example[0]
On Tuesday, August 3, 2021 my dinner cost $37.63.
When passing "sr-Cryl-RS", the correct corresponding resource files are found and the localization applied:
dotnet run --project .\example\example.csproj sr-Cryl-RS
warn: Localization.Example[0]
Здраво пријатељи, ".NЕТ" девелопер заједница је узбуђена што вас види овде!
warn: Localization.Example[0]
У уторак, 03. август 2021. моја вечера је коштала 38 RSD.
The sample application does not provide resource files for "fr-CA", but when called with that culture, the non-localized resource files are used.
تحذير
Since the culture is found but the correct resource files are not, when formatting is applied you end up with partial localization:
dotnet run --project .\example\example.csproj fr-CA
warn: Localization.Example[0]
Hi friends, the ".NET" developer community is excited to see you here!
warn: Localization.Example[0]
On mardi 3 août 2021 my dinner cost 37,63 $.
تعرف على كيفية استخدام الموارد المشتركة الثابتة والديناميكية لإنشاء واجهة مستخدم MAUI. وشاهد كيف يمكن للأنماط أن تجعل واجهة المستخدم متسقة ويمكن الوصول إليها.