Compartilhar via


Localização no .NET

A localização é o processo de traduzir os recursos de um aplicativo em versões localizadas para cada cultura que o aplicativo dará suporte. Você deve prosseguir para a etapa de localização somente depois de concluir a etapa de revisão de localizabilidade para verificar se o aplicativo globalizado está pronto para localização.

Um aplicativo pronto para localização é separado em dois blocos conceituais: um bloco que contém todos os elementos da interface do usuário e um bloco que contém código executável. O bloco de interface do usuário contém apenas elementos de interface do usuário localizáveis, como cadeias de caracteres, mensagens de erro, caixas de diálogo, menus, recursos de objeto inserido e assim por diante para a cultura neutra. O bloco de código contém apenas o código do aplicativo a ser usado por todas as culturas com suporte. O common language runtime dá suporte a um modelo de recurso de assembly satélite que separa o código executável do aplicativo de seus recursos. Para obter mais informações sobre como implementar esse modelo, consulte Recursos no .NET.

Para cada versão localizada do aplicativo, adicione um novo assembly satélite que contenha o bloco de interface do usuário localizada traduzido para o idioma apropriado para a cultura de destino. O bloco de código para todas as culturas deve permanecer o mesmo. A combinação de uma versão localizada do bloco de interface do usuário com o bloco de código produz uma versão localizada do seu aplicativo.

Neste artigo, você aprenderá a usar as implementações IStringLocalizer<T> e IStringLocalizerFactory. Todo o código-fonte de exemplo neste artigo depende dos pacotes NuGet Microsoft.Extensions.Localization e Microsoft.Extensions.Hosting. Para obter mais informações sobre hospedagem, consulte o Host Genérico do .NET.

Arquivos de recurso

O mecanismo primário para isolar cadeias de caracteres localizáveis é com arquivos de recurso. Um arquivo de recurso é um arquivo XML com a extensão de arquivo .resx . Os arquivos de recurso são traduzidos antes da execução do aplicativo consumidor. Em outras palavras, eles representam o conteúdo traduzido em estado inativo. Um nome de arquivo de recurso geralmente contém um identificador de local e assume o seguinte formato:

<FullTypeName><.Locale>.resx

Em que:

  • O <FullTypeName> representa recursos localizáveis para um tipo específico.
  • O opcional <.Locale> representa a localidade do conteúdo do arquivo de recurso.

Especificando localidades

A localidade deve definir o idioma, no mínimo, mas também pode definir a cultura (idioma regional) e até mesmo o país ou a região. Esses segmentos geralmente são delimitados pelo - caractere. Com a especificidade agregada de uma cultura, as regras de "fallback de cultura" são aplicadas onde as melhores correspondências são priorizadas. A localidade deve ser mapeada para uma marca de idioma bem conhecida. Para obter mais informações, consulte CultureInfo.Name.

Cenários de fallback de cultura

Imagine que seu aplicativo localizado dá suporte a várias localizações serbias e tem os seguintes arquivos de recurso para o MessageService:

Arquivo Língua regional Código do país
MessageService.sr-Cyrl-RS.resx (Cirílico, Sérvia) RS
MessageService.sr-Cyrl.resx Cirílico
MessageService.sr-Latn-BA.resx (Latino, Bósnia &Herzegovina) Bacharelado em Artes
MessageService.sr-Latn-ME.resx (Latim, Montenegro) ME
MessageService.sr-Latn-RS.resx (Latino, Sérvia) RS
MessageService.sr-Latn.resx Latim
MessageService.sr.resx Latim
MessageService.resx

O idioma regional padrão para o idioma.

Quando o aplicativo está em execução com o CultureInfo.CurrentCulture definido como cultura de "sr-Cyrl-RS", a localização tenta resolver os arquivos na seguinte ordem:

  1. MessageService.sr-Cyrl-RS.resx
  2. MessageService.sr-Cyrl.resx
  3. MessageService.sr.resx
  4. MessageService.resx

No entanto, se o aplicativo estiver em execução com o CultureInfo.CurrentCulture definido como cultura de "sr-Latn-BA", a localização tentará resolver os arquivos na seguinte ordem:

  1. MessageService.sr-Latn-BA.resx
  2. MessageService.sr-Latn.resx
  3. MessageService.sr.resx
  4. MessageService.resx

A regra de "fallback de cultura" ignorará as localidades quando não houver as respectivas correspondências, o que significa que o arquivo de recurso número quatro será selecionado se não for possível encontrar uma correspondência. Se a cultura fosse definida como "fr-FR", a localização acabaria recaindo sobre o arquivo MessageService.resx, o que pode ser problemático. Para obter mais informações, consulte o processo de fallback de recursos.

Pesquisa de recursos

Os arquivos de recurso são resolvidos automaticamente como parte de uma rotina de pesquisa. Se o nome do arquivo de projeto for diferente do namespace raiz do projeto, o nome do assembly poderá ser diferente. Isso pode impedir que a pesquisa de recursos seja bem-sucedida. Para resolver essa incompatibilidade, use RootNamespaceAttribute para fornecer uma dica aos serviços de localização. Quando fornecido, ele é usado durante a pesquisa de recursos.

O projeto de exemplo é denominado example.csproj, que cria um example.dll e example.exe— no entanto, o Localization.Example namespace é usado. Aplique um assembly atributo de nível para corrigir essa incompatibilidade:

[assembly: RootNamespace("Localization.Example")]

Registrar serviços de localização

Para registrar os serviços de localização, chame um dos métodos de extensão AddLocalization durante a configuração dos serviços. Isso habilitará a DI (injeção de dependência) dos seguintes tipos:

Configurar opções de localização

A sobrecarga AddLocalization(IServiceCollection, Action<LocalizationOptions>) aceita um parâmetro setupAction do tipo Action<LocalizationOptions>. Isso permite que você configure opções de localização.

HostApplicationBuilder builder = Host.CreateApplicationBuilder(args);

builder.Services.AddLocalization(options =>
{
    options.ResourcesPath = "Resources";
});

// Omitted for brevity.

Os arquivos de recurso podem residir em qualquer lugar em um projeto, mas há práticas comuns em vigor que provaram ser bem-sucedidas. Na maioria das vezes, o caminho de menor resistência é seguido. O código C# anterior:

  • Cria o construtor do aplicativo de host padrão.
  • Chama AddLocalization na coleção de serviços, especificando LocalizationOptions.ResourcesPath como "Resources".

Isso faria com que os serviços de localização procurassem no diretório Recursos arquivos de recursos.

Usar IStringLocalizer<T> e IStringLocalizerFactory

Depois de registrar (e opcionalmente configurar) os serviços de localização, você pode usar os seguintes tipos com DI:

Para criar um serviço de mensagem capaz de retornar cadeias de caracteres localizadas, considere o seguinte MessageService:

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;
    }
}

No código anterior do C#:

  • Um IStringLocalizer<MessageService> localizer campo é declarado.
  • O construtor primário define um IStringLocalizer<MessageService> parâmetro e o captura como um localizer argumento.
  • O método GetGreetingMessage invoca IStringLocalizer.Item[String] passando "GreetingMessage" como argumento.

O IStringLocalizer também dá suporte a recursos de cadeia de caracteres parametrizados, considere o seguinte 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;
    }
}

No código anterior do C#:

  • Um IStringLocalizer _localizer campo é declarado.
  • O construtor primário usa um IStringLocalizerFactory parâmetro, que é usado para criar um IStringLocalizer do ParameterizedMessageService tipo e o atribui ao _localizer campo.
  • O GetFormattedMessage método invoca IStringLocalizer.Item[String, Object[]], passando "DinnerPriceFormat", um dateTime objeto e dinnerPrice como argumentos.

Importante

O IStringLocalizerFactory não é necessário. Em vez disso, é preferível que os serviços de consumo exijam a IStringLocalizer<T>.

Ambos os IStringLocalizer.Item[] indexadores retornam um LocalizedString, que tem conversões implícitas para string?.

Coloque tudo junto

Para exemplificar um aplicativo usando os serviços de mensagem, juntamente com a localização e os arquivos de recurso, considere o seguinte arquivo Program.cs :

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();

No código anterior do C#:

  • O RootNamespaceAttribute define "Localization.Example" como o namespace raiz.
  • O Console.OutputEncoding é atribuído a Encoding.Unicode.
  • Quando um único argumento é passado para args, o CultureInfo.CurrentCulture e o CultureInfo.CurrentUICulture são atribuídos ao resultado de CultureInfo.GetCultureInfo(String), considerando o arg[0].
  • O Host é criado com padrões.
  • Os serviços de localização, MessageService, e o ParameterizedMessageService estão registrados no IServiceCollection para DI.
  • Para remover o ruído, o log é configurado para ignorar qualquer nível de log inferior a um aviso.
  • O MessageService é resolvido na instância IServiceProvider e a mensagem resultante é registrada.
  • O ParameterizedMessageService é resolvido na instância IServiceProvider e a mensagem formatada resultante é registrada.

Cada uma das *MessageService classes define um conjunto de arquivos .resx , cada uma com uma única entrada. Aqui está o conteúdo de exemplo para os MessageService arquivos de recurso, começando com 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>

Aqui está o conteúdo de exemplo para os ParameterizedMessageService arquivos de recurso, começando com 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>

Dica

Todos os comentários, esquemas e <resheader> elementos XML do arquivo de recurso são intencionalmente omitidos por brevidade.

Execuções de exemplo

Os exemplos de execução a seguir mostram as várias saídas localizadas, dadas as localizações-alvo.

Considere "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 ¤.

Ao omitir um argumento para a CLI do .NET para executar o projeto, a cultura do sistema padrão é usada, nesse 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.

Ao passar "sr-Cryl-RS", os arquivos de recurso correspondentes corretos são encontrados e a localização aplicada:

dotnet run --project .\example\example.csproj sr-Cryl-RS

warn: Localization.Example[0]
      Здраво пријатељи, ".NЕТ" девелопер заједница је узбуђена што вас види овде!
warn: Localization.Example[0]
      У уторак, 03. август 2021. моја вечера је коштала 38 RSD.

O aplicativo de exemplo não fornece arquivos de recurso para "fr-CA", mas quando chamado com essa cultura, os arquivos de recurso não localizados são usados.

Aviso

Uma vez que a cultura é identificada, mas os arquivos de recurso corretos não são encontrados, a aplicação de formatação resulta em uma localização 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 $.

Consulte também