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:

  1. MessageService.sr-Cyrl-RS.resx
  2. MessageService.sr-Cyrl.resx
  3. MessageService.sr.resx
  4. 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:

  1. MessageService.sr-Latn-BA.resx
  2. MessageService.sr-Latn.resx
  3. MessageService.sr.resx
  4. 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:

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 argumento localizer.
  • 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 elemento IStringLocalizer a partir del tipo ParameterizedMessageService, y lo asigna al campo _localizer.
  • El método GetFormattedMessage invoca a IStringLocalizer.Item[String, Object[]], lo que pasa "DinnerPriceFormat", un objeto dateTime, y dinnerPrice 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:

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 $.

Vea también