Localización en .NET
La localización es el proceso de traducción de los recursos de una aplicación en versiones localizadas para cada referencia cultural que la aplicación admite. Debe seguir con el paso de localización solo después de completar el paso Revisión de localizabilidad para verificar que la aplicación globalizada está lista para su localización.
Una aplicación que está lista para la localización se divide en dos bloques conceptuales: un bloque que contiene todos los elementos de la interfaz de usuario y otro que contiene código ejecutable. El bloque de la interfaz de usuario contiene solo los elementos localizables de dicha interfaz, como cadenas, mensajes de error, cuadros de diálogo, menús, recursos de objetos incrustados, etc., para la referencia cultural neutra. El bloque de código contiene solo el código de la aplicación que usarán todas las referencias culturales admitidas. Common Language Runtime admite un modelo de recursos de ensamblado satélite que separa el código ejecutable de una aplicación de sus recursos. Para obtener más información sobre la implementación de este modelo, vea Recursos de .NET.
Para cada versión localizada de la aplicación, agregue un nuevo ensamblado satélite que contenga el bloque de la interfaz de usuario localizada traducida en el idioma apropiado para la referencia cultural de destino. El bloque de código de todas las referencias culturales debe permanecer igual. La combinación de una versión localizada del bloque de interfaz de usuario con el bloque de código produce una versión localizada de la aplicación.
En este artículo, aprenderá a usar las implementaciones IStringLocalizer<T> y IStringLocalizerFactory. Todo el código fuente de ejemplo de este artículo se basa en los paquetes NuGet Microsoft.Extensions.Localization
y Microsoft.Extensions.Hosting
. Para obtener más información sobre el hospedaje, consulte Host genérico de .NET.
Archivos de recursos
El mecanismo principal para aislar cadenas localizables es con archivos de recursos. Un archivo de recursos es un archivo XML con la extensión de archivo .resx. Los archivos de recursos se traducen antes de la ejecución de la aplicación consumidora, es decir, representan el contenido traducido en reposo. Un nombre de archivo de recursos contiene normalmente un identificador de configuración regional y adopta el formato siguiente:
<FullTypeName><.Locale>.resx
Donde:
<FullTypeName>
representa recursos localizables para un tipo específico.- El elemento opcional
<.Locale>
representa la configuración regional del contenido del archivo de recursos.
Especificación de configuraciones regionales
La configuración regional debe definir el idioma, como mínimo, pero también puede definir la referencia cultural (idioma regional), e incluso el país o la región. Normalmente, estos segmentos están delimitados por el carácter -
. Con la especificidad agregada de una referencia cultural, se aplican las reglas de "reserva de la referencia cultural", donde se priorizan las mejores coincidencias. La configuración regional debe asignarse a una etiqueta de idioma conocida. Para obtener más información, vea CultureInfo.Name.
Escenarios de reserva de la referencia cultural
Imagine que la aplicación localizada admite varias configuraciones regionales de serbio y tiene los archivos de recursos siguientes para su MessageService
:
Archivo | Idioma regional | Código de país |
---|---|---|
MessageService.sr-Cyrl-RS.resx | (Cirílico, Serbia) | RS |
MessageService.sr-Cyrl.resx | Cirílico | |
MessageService.sr-Latn-BA.resx | (Latino, Bosnia y Herzegovina) | BA |
MessageService.sr-Latn-ME.resx | (Latino, Montenegro) | ME |
MessageService.sr-Latn-RS.resx | (Latino, Serbia) | RS |
MessageService.sr-Latn.resx | Latín | |
MessageService.sr.resx | † Latino | |
MessageService.resx |
† Idioma regional predeterminado para el idioma.
Cuando la aplicación se ejecuta con CultureInfo.CurrentCulture establecido en una referencia cultural de "sr-Cyrl-RS"
, la localización intenta resolver los archivos en el orden siguiente:
- MessageService.sr-Cyrl-RS.resx
- MessageService.sr-Cyrl.resx
- MessageService.sr.resx
- MessageService.resx
Sin embargo, si la aplicación se ejecutaba con CultureInfo.CurrentCulture establecido en una referencia cultural de "sr-Latn-BA"
, la localización intenta resolver los archivos en el orden siguiente:
- MessageService.sr-Latn-BA.resx
- MessageService.sr-Latn.resx
- MessageService.sr.resx
- MessageService.resx
La regla de "reserva de la referencia cultural" omitirá las configuraciones regionales cuando no haya coincidencias correspondientes, lo que significa que se selecciona el archivo de recursos número cuatro si no encuentra ninguna coincidencia. Si la referencia cultural se ha establecido en "fr-FR"
, la localización acabaría en el archivo MessageService.resx, lo que puede resultar problemático. Para obtener más información, consulte la sección Proceso de reserva de recursos.
Búsqueda de recursos
Los archivos de recursos se resuelven automáticamente como parte de una rutina de búsqueda. Si el nombre de archivo del proyecto es diferente del espacio de nombres raíz del proyecto, el nombre del ensamblado puede diferir. Esto puede impedir que la búsqueda de recursos se realice correctamente. Para solucionar este error de coincidencia, use RootNamespaceAttribute para hacer una sugerencia a los servicios de localización. Si se hace, se usará durante la búsqueda de recursos.
El proyecto de ejemplo se denomina example.csproj, que crea un example.dll y un example.exe -sin embargo, se utiliza el Localization.Example
espacio de nombres. Aplique un atributo de nivel assembly
para corregir este error de coincidencia:
[assembly: RootNamespace("Localization.Example")]
Registro de servicios de localización
Para registrar los servicios de localización, llame a uno de los métodos de extensión AddLocalization durante la configuración de los servicios. Habilitará la inserción de dependencias (DI) de los tipos siguientes:
- Microsoft.Extensions.Localization.IStringLocalizer<T>
- Microsoft.Extensions.Localization.IStringLocalizerFactory
Configuración de las opciones de localización
La sobrecarga AddLocalization(IServiceCollection, Action<LocalizationOptions>) acepta un parámetro setupAction
de tipo Action<LocalizationOptions>
. Esto le permite configurar las opciones de localización.
HostApplicationBuilder builder = Host.CreateApplicationBuilder(args);
builder.Services.AddLocalization(options =>
{
options.ResourcesPath = "Resources";
});
// Omitted for brevity.
Los archivos de recursos pueden estar en cualquier lugar de un proyecto, pero existen prácticas habituales que han demostrado funcionar. Casi siempre se sigue la ruta de menor resistencia. El código de C# anterior:
- Crea el generador de aplicación de host predeterminado.
- Llama a
AddLocalization
en la colección de servicios, lo que especifica LocalizationOptions.ResourcesPath como"Resources"
.
Esto provocaría que los servicios de localización busquen archivos de recursos en el directorio Recursos.
Uso de IStringLocalizer<T>
y IStringLocalizerFactory
Una vez que haya registrado (y configurado opcionalmente) los servicios de localización, puede usar los tipos siguientes con DI:
Para crear un servicio de mensajes capaz de devolver cadenas localizadas, tenga en cuenta el elemento MessageService
siguiente:
using System.Diagnostics.CodeAnalysis;
using Microsoft.Extensions.Localization;
namespace Localization.Example;
public sealed class MessageService(IStringLocalizer<MessageService> localizer)
{
[return: NotNullIfNotNull(nameof(localizer))]
public string? GetGreetingMessage()
{
LocalizedString localizedString = localizer["GreetingMessage"];
return localizedString;
}
}
En el código de C# anterior:
- Se declara un campo
IStringLocalizer<MessageService> localizer
. - El constructor principal define un parámetro
IStringLocalizer<MessageService>
y lo captura como argumentolocalizer
. - El método
GetGreetingMessage
invoca a IStringLocalizer.Item[String], lo que pasa"GreetingMessage"
como argumento.
IStringLocalizer
también admite recursos de cadena con parámetros. Tenga en cuenta el elemento ParameterizedMessageService
siguiente:
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;
}
}
En el código de C# anterior:
- Se declara un campo
IStringLocalizer _localizer
. - El constructor toma un parámetro
IStringLocalizerFactory
, que se usa para crear un elementoIStringLocalizer
a partir del tipoParameterizedMessageService
, y lo asigna al campo_localizer
. - El método
GetFormattedMessage
invoca a IStringLocalizer.Item[String, Object[]], lo que pasa"DinnerPriceFormat"
, un objetodateTime
, ydinnerPrice
como argumentos.
Importante
El elemento IStringLocalizerFactory
no es necesario. En su lugar, es preferible que el consumo de servicios requiera IStringLocalizer<T>.
Ambos indizadores IStringLocalizer.Item[] devuelven un elemento LocalizedString, que tienen conversiones implícitas a string?
.
Colocación de todo junto
Para ejemplificar una aplicación que use ambos servicios de mensajes, junto con archivos de localización y recursos, considere el archivo Program.cs siguiente:
using System.Globalization;
using Localization.Example;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;
using Microsoft.Extensions.Localization;
using Microsoft.Extensions.Logging;
using static System.Console;
using static System.Text.Encoding;
[assembly: RootNamespace("Localization.Example")]
OutputEncoding = Unicode;
if (args is [var cultureName])
{
CultureInfo.CurrentCulture =
CultureInfo.CurrentUICulture =
CultureInfo.GetCultureInfo(cultureName);
}
HostApplicationBuilder builder = Host.CreateApplicationBuilder(args);
builder.Services.AddLocalization();
builder.Services.AddTransient<MessageService>();
builder.Services.AddTransient<ParameterizedMessageService>();
builder.Logging.SetMinimumLevel(LogLevel.Warning);
using IHost host = builder.Build();
IServiceProvider services = host.Services;
ILogger logger =
services.GetRequiredService<ILoggerFactory>()
.CreateLogger("Localization.Example");
MessageService messageService =
services.GetRequiredService<MessageService>();
logger.LogWarning(
"{Msg}",
messageService.GetGreetingMessage());
ParameterizedMessageService parameterizedMessageService =
services.GetRequiredService<ParameterizedMessageService>();
logger.LogWarning(
"{Msg}",
parameterizedMessageService.GetFormattedMessage(
DateTime.Today.AddDays(-3), 37.63));
await host.RunAsync();
En el código de C# anterior:
- RootNamespaceAttribute establece
"Localization.Example"
como el espacio de nombres raíz. - Console.OutputEncoding se asigna a Encoding.Unicode.
- Cuando se pasa un único argumento a
args
, CultureInfo.CurrentCulture y CultureInfo.CurrentUICulture se asignan al resultado de CultureInfo.GetCultureInfo(String) dado el elementoarg[0]
. - Host se crea con los valores predeterminados.
- Los servicios de localización,
MessageService
yParameterizedMessageService
se registran en el elementoIServiceCollection
para la DI. - El registro se configura para omitir cualquier nivel de registro inferior a una advertencia a fin de quitar ruido.
MessageService
se resuelve desde la instanciaIServiceProvider
y se registra el mensaje resultante.ParameterizedMessageService
se resuelve desde la instanciaIServiceProvider
y se registra el mensaje con formato resultante.
Cada una de las clases de *MessageService
define un conjunto de archivos .resx, cada uno con una sola entrada. Este es el contenido de ejemplo de los archivos de recursos de MessageService
, empezando por 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>
Este es el contenido de ejemplo de los archivos de recursos de ParameterizedMessageService
, empezando por ParameterizedMessageService.resx:
<?xml version="1.0" encoding="utf-8"?>
<root>
<data name="DinnerPriceFormat" xml:space="preserve">
<value>On {0:D} my dinner cost {1:C}.</value>
</data>
</root>
ParameterizedMessageService.sr-Cyrl-RS.resx:
<?xml version="1.0" encoding="utf-8"?>
<root>
<data name="DinnerPriceFormat" xml:space="preserve">
<value>У {0:D} моја вечера је коштала {1:C}.</value>
</data>
</root>
ParameterizedMessageService.sr-Latn.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>
Sugerencia
Todos los comentarios, esquemas y elementos <resheader>
XML del archivo de recursos se omiten intencionadamente por brevedad.
Ejecuciones de ejemplo
En las ejecuciones de ejemplo a continuación se muestran las distintas salidas localizadas, dadas las configuraciones regionales de destino.
Tenga en cuenta "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 ¤.
Cuando se omite un argumento a la .NET CLI para ejecutar el proyecto, la cultura por defecto del sistema se utiliza-en este caso "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.
Al pasar "sr-Cryl-RS"
, se encuentran los archivos de recursos correspondientes correctos y se aplica la localización:
dotnet run --project .\example\example.csproj sr-Cryl-RS
warn: Localization.Example[0]
Здраво пријатељи, ".NЕТ" девелопер заједница је узбуђена што вас види овде!
warn: Localization.Example[0]
У уторак, 03. август 2021. моја вечера је коштала 38 RSD.
La aplicación de ejemplo no proporciona archivos de recursos para "fr-CA"
, pero, cuando se llama con esa referencia cultural, se usan los archivos de recursos no localizados.
Advertencia
Puesto que se encuentra la referencia cultural, pero no los archivos de recursos correctos, al aplicar el formato, obtendrá una localización parcial:
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 $.