Skip to main content

O guia completo da localização no ASP.NET Core

De IStringLocalizer à produção: configure a localização baseada em recursos no ASP.NET Core e automatize as traduções com IA.

1

Ativar os serviços de localização

Registre os serviços em Program.cs com AddLocalization(), configure as culturas compatíveis e adicione o middleware de localização das requisições. Isto liga todo o pipeline da aplicação ASP.NET Core.

AddLocalization() registra IStringLocalizer e IStringLocalizerFactory no contêiner de DI. ResourcesPath indica ao framework onde encontrar os arquivos .resx. AddViewLocalization() ativa IViewLocalizer nas vistas Razor e AddDataAnnotationsLocalization() ativa mensagens de validação localizadas.
Program.cs
using Microsoft.AspNetCore.Localization;
using System.Globalization;

var builder = WebApplication.CreateBuilder(args);

// 1. Register localization services
builder.Services.AddLocalization(o => o.ResourcesPath = "Resources");

// 2. Add MVC with view/data-annotation localization
builder.Services.AddControllersWithViews()
    .AddViewLocalization()
    .AddDataAnnotationsLocalization();

var app = builder.Build();

// 3. Configure supported cultures
var supportedCultures = new[] { "en", "de", "ja", "es", "pt-BR" }
    .Select(c => new CultureInfo(c)).ToArray();

app.UseRequestLocalization(new RequestLocalizationOptions
{
    DefaultRequestCulture = new RequestCulture("en"),
    SupportedCultures = supportedCultures,
    SupportedUICultures = supportedCultures,
});

app.UseStaticFiles();
app.UseRouting();
app.MapControllers();
app.Run();
2

Criar arquivos de recursos RESX

ASP.NET Core utiliza arquivos RESX —recursos XML— nas traduções. Crie um arquivo por cultura e classe: HomeController.en.resx, HomeController.de.resx, etc. O framework resolve o arquivo correto segundo a cultura da requisição atual.

Resources/Controllers/HomeController.{culture}.resx
<!-- Resources/Controllers/HomeController.en.resx -->
<?xml version="1.0" encoding="utf-8"?>
<root>
  <data name="Welcome" xml:space="preserve">
    <value>Welcome to our application</value>
  </data>
  <data name="Greeting" xml:space="preserve">
    <value>Hello, {0}!</value>
  </data>
</root>

<!-- Resources/Controllers/HomeController.de.resx -->
<?xml version="1.0" encoding="utf-8"?>
<root>
  <data name="Welcome" xml:space="preserve">
    <value>Willkommen in unserer Anwendung</value>
  </data>
  <data name="Greeting" xml:space="preserve">
    <value>Hallo, {0}!</value>
  </data>
</root>
Utilize uma classe SharedResource com seus arquivos RESX para strings compartilhadas entre controladores e vistas —etiquetas de botões, elementos de navegação e mensagens de validação comuns—. Assim evita duplicar chaves em dezenas de arquivos específicos.
Shared resources for cross-cutting strings
<!-- Resources/SharedResource.en.resx — shared across controllers -->
<?xml version="1.0" encoding="utf-8"?>
<root>
  <data name="AppName" xml:space="preserve">
    <value>My Application</value>
  </data>
  <data name="Save" xml:space="preserve"><value>Save</value></data>
  <data name="Cancel" xml:space="preserve"><value>Cancel</value></data>
</root>

// Marker class (empty — only used for type lookup)
namespace MyApp;
public class SharedResource { }
3

Utilizar IStringLocalizer em controladores e serviços

Injete IStringLocalizer&lt;T&gt; em qualquer controlador, serviço ou middleware através de injeção de dependências. O parâmetro genérico T determina o RESX a carregar. Utilize a sintaxe localizer["Key"] para obter strings traduzidas, com parâmetros de formato opcionais.

Controllers/HomeController.cs
using Microsoft.AspNetCore.Mvc;
using Microsoft.Extensions.Localization;

public class HomeController(
    IStringLocalizer<HomeController> localizer,
    IStringLocalizer<SharedResource> shared) : Controller
{
    public IActionResult Index()
    {
        ViewData["Welcome"] = localizer["Welcome"];
        ViewData["AppName"] = shared["AppName"];

        // String interpolation with format parameters
        var greeting = localizer["Greeting", User.Identity?.Name ?? "Guest"];
        return View(new HomeViewModel { Greeting = greeting });
    }
}
Se IStringLocalizer devolver o nome da chave em vez do valor, verifique: 1) se o nome do RESX corresponde ao espaço de nomes da classe; 2) se ResourcesPath em AddLocalization() aponta para a pasta correta; 3) se Build Action do RESX está definido como Embedded Resource no Visual Studio.
4

Configurar o middleware de cultura das requisições

ASP.NET Core determina a cultura por meio de uma cadeia de provedores: string de consulta, cookie e cabeçalho Accept-Language, nessa ordem. Você pode adicionar provedores personalizados, por exemplo, para ler a cultura de um segmento de rota como /de/home.

Custom RouteDataRequestCultureProvider
// Culture resolved in order: QueryString, Cookie, Accept-Language
// Custom provider: read culture from URL route segment /de/home
public class RouteDataRequestCultureProvider : RequestCultureProvider
{
    public override Task<ProviderCultureResult?> DetermineProviderCultureResult(
        HttpContext httpContext)
    {
        var culture = httpContext.GetRouteValue("culture")?.ToString();
        if (string.IsNullOrEmpty(culture))
            return NullProviderCultureResult;
        return Task.FromResult<ProviderCultureResult?>(
            new ProviderCultureResult(culture));
    }
}

// Register in Program.cs (route provider first = highest priority):
app.UseRequestLocalization(new RequestLocalizationOptions
{
    DefaultRequestCulture = new RequestCulture("en"),
    SupportedCultures = supportedCultures,
    SupportedUICultures = supportedCultures,
    RequestCultureProviders = new List<IRequestCultureProvider>
    {
        new RouteDataRequestCultureProvider(),
        new QueryStringRequestCultureProvider(),
        new CookieRequestCultureProvider(),
        new AcceptLanguageHeaderRequestCultureProvider(),
    }
});
Language Switcher Action
// Language switcher: persist choice in cookie
[HttpPost]
public IActionResult SetLanguage(string culture, string returnUrl)
{
    Response.Cookies.Append(
        CookieRequestCultureProvider.DefaultCookieName,
        CookieRequestCultureProvider.MakeCookieValue(new RequestCulture(culture)),
        new CookieOptions { Expires = DateTimeOffset.UtcNow.AddYears(1) });
    return LocalRedirect(returnUrl);
}
A ordem do middleware é importante. UseRequestLocalization() deve ser chamado depois de UseRouting(), mas antes de UseEndpoints() ou MapControllers(). Se surgir demasiado tarde, a cultura não estará definida ao executar os controladores.
5

Localizar anotações de dados

Os atributos de validação, como [Required], [StringLength] e [Display], podem ser localizados definindo ErrorMessage ou Name com nomes de chaves RESX. Chame AddDataAnnotationsLocalization() em Program.cs para ativar esta funcionalidade.

ViewModels/RegisterViewModel.cs
using System.ComponentModel.DataAnnotations;

public class RegisterViewModel
{
    [Required(ErrorMessage = "NameRequired")]
    [Display(Name = "FullName")]
    [StringLength(100, ErrorMessage = "NameLength", MinimumLength = 2)]
    public string Name { get; set; } = string.Empty;

    [Required(ErrorMessage = "EmailRequired")]
    [EmailAddress(ErrorMessage = "EmailInvalid")]
    [Display(Name = "EmailAddress")]
    public string Email { get; set; } = string.Empty;

    [Required(ErrorMessage = "PasswordRequired")]
    [StringLength(100, ErrorMessage = "PasswordLength", MinimumLength = 8)]
    [Display(Name = "Password")]
    public string Password { get; set; } = string.Empty;
}
// RESX keys map to ErrorMessage/Name values:
// RegisterViewModel.de.resx: NameRequired = "Name ist erforderlich"
// RegisterViewModel.de.resx: FullName = "Vollständiger Name"
A localização das anotações de dados utiliza o nome da classe ViewModel para encontrar os RESX, não o controlador. Em RegisterViewModel, o framework procura Resources/ViewModels/RegisterViewModel.de.resx. Se os arquivos tiverem o nome do controlador, as mensagens de validação não serão localizadas.
6

Tratar plurais e mensagens ICU

.NET não inclui regras de plural como ICU. Em casos simples, utilize chaves RESX separadas —ItemCount_One e ItemCount_Other— e uma seleção no código. Para compatibilidade completa com ICU MessageFormat e todas as categorias CLDR, utilize a biblioteca MessageFormat.NET.

Plural handling strategies
// Option 1: Separate RESX keys with code switch
// HomeController.en.resx: ItemCount_One = "You have {0} item"
// HomeController.en.resx: ItemCount_Other = "You have {0} items"
public string GetItemCount(int count)
{
    var key = count == 1 ? "ItemCount_One" : "ItemCount_Other";
    return _localizer[key, count];
}

// Option 2: ICU MessageFormat (dotnet add package MessageFormat.NET)
using Jeffijoe.MessageFormat;
var formatter = new MessageFormatter();

var pattern = "{count, plural, one {# item} other {# items}} in your cart";
var result = formatter.FormatMessage(pattern,
    new Dictionary<string, object> { { "count", 5 } });
// => "5 items in your cart"

// Arabic: 6 plural forms (zero, one, two, few, many, other)
var arPattern = @"{count, plural,
    zero {لا عناصر} one {عنصر واحد} two {عنصران}
    few {# عناصر} many {# عنصرًا} other {# عنصر}}";
Nunca utilize count == 1 para detectar o singular. O francês considera 0 singular. O russo tem formas separadas para 'few' e 'many'. O árabe tem seis categorias. Utilize regras que reconheçam CLDR ou uma biblioteca como MessageFormat.NET.
7

Localizar vistas Razor

Utilize IViewLocalizer nas vistas Razor através de @inject. Resolve RESX com base no caminho do arquivo da vista. Em strings com marcação HTML segura, utilize IHtmlLocalizer. Tag Helpers como asp-for e asp-validation-for utilizam automaticamente os atributos Display e ErrorMessage localizados.

Views/Home/Index.cshtml
@using Microsoft.AspNetCore.Mvc.Localization
@inject IViewLocalizer Localizer
@inject IHtmlLocalizer<SharedResource> SharedHtml

<h1>@Localizer["Welcome"]</h1>
<p>@Localizer["Greeting", User.Identity?.Name]</p>

@* IHtmlLocalizer: does NOT escape — use for RESX values with HTML *@
<p>@SharedHtml["TermsNotice"]</p>

@* Tag Helpers auto-localize Display/ErrorMessage attributes *@
<form asp-action="Register">
    <label asp-for="Name"></label>
    <input asp-for="Name" />
    <span asp-validation-for="Name"></span>
    <button type="submit">@Localizer["Submit"]</button>
</form>
IViewLocalizer resolve RESX pelo caminho da vista: Views/Home/Index.cshtml procura Resources/Views/Home/Index.de.resx. Se quiser compartilhar strings entre vistas, injete separadamente IStringLocalizer&lt;SharedResource&gt;.
8

Automatizar traduções RESX

Depois de concluir a configuração da localização, traduza os RESX com IA. No IDE, peça ao assistente para traduzir o RESX de origem ou utilize a CLI do i18n Agent no pipeline de CI/CD para manter as traduções sincronizadas.

Terminal
# In your IDE, ask your AI assistant:
> Translate Resources/Controllers/HomeController.en.resx to German, Japanese, Spanish

# HomeController.de.resx created (1.2s)
# HomeController.ja.resx created (1.5s)
# HomeController.es.resx created (1.1s)

# Or use the CLI in CI/CD:
npx i18n-agent translate Resources/Controllers/HomeController.en.resx --lang de,ja,es
Traduza de forma incremental: ao adicionar chaves ao RESX de origem, traduza apenas as diferenças em vez de voltar a gerar todos os arquivos. Assim preserva traduções revisadas por pessoas e minimiza alterações desnecessárias.

Automatizar a qualidade das traduções

Detecte chaves em falta e marcadores danificados antes da publicação com i18n-validate. Teste a interface com pseudotraduções através de i18n-pseudo antes de chegarem as traduções reais.

Corrigir o fallback regional com LocaleChain.NET

A hierarquia CultureInfo.Parent integrada em .NET utiliza apenas truncamento BCP 47: pt-BR recorre a pt e depois InvariantCulture, ignorando pt-PT. LocaleChain.NET oferece cadeias configuráveis por localidade para todo o ecossistema .NET.

Sem LocaleChain.NET, um usuário pt-BR vê inglês quando falta uma string em português, mesmo que exista uma tradução pt-PT completa. O mesmo problema afeta es-MX —ignora es-419—, zh-Hant —ignora zh-Hans— e dezenas de variantes.
Terminal
dotnet add package I18nAgent.LocaleChain
Program.cs
using I18nAgent.LocaleChain;
var builder = WebApplication.CreateBuilder(args);
builder.Services.AddLocalization(o => o.ResourcesPath = "Resources");

// Zero-config: built-in chains (pt-BR -> pt-PT -> pt -> en, etc.)
LocaleChain.Configure();

// Or customize specific chains:
LocaleChain.Configure(new Dictionary<string, string[]>
{
    ["pt-BR"] = new[] { "pt-PT", "pt", "en" },
    ["es-MX"] = new[] { "es-419", "es", "en" },
});

// Register the chain-aware string localizer
builder.Services.AddSingleton(
    typeof(IStringLocalizer<>),
    typeof(LocaleChainStringLocalizer<>));

Erros frequentes

Cultura não definida na requisição

As traduções mostram sempre o idioma predefinido. Confirme se UseRequestLocalization() é chamado no pipeline de middleware e se os fornecedores estão configurados. Verifique se o navegador envia Accept-Language. Teste ?culture=de na consulta para confirmar o funcionamento.

Arquivo RESX não encontrado

IStringLocalizer devolve o nome da chave em vez do valor. A causa mais frequente é o nome do RESX não corresponder ao caminho completo do espaço de nomes da classe relativamente a ResourcesPath. Ative o registro de depuração de Microsoft.Extensions.Localization para ver os caminhos procurados.

Ordem incorreta do middleware

UseRequestLocalization() deve surgir antes de UseEndpoints() e MapControllers(). Se estiver depois, a cultura não é definida ao executar os controladores. No modelo de alojamento mínimo de .NET 6 ou posterior, chame-o antes de app.MapControllers().

A thread em segundo plano utiliza a cultura errada

CultureInfo.CurrentCulture e CurrentUICulture são específicos de cada thread. As tarefas em segundo plano —Task.Run e serviços alojados— herdam a cultura do conjunto de threads, não a da requisição. Capture e defina explicitamente a cultura ao despachar trabalho em segundo plano.

Estrutura de projeto recomendada

Project Structure
MyAspNetApp/
├── Controllers/
│   └── HomeController.cs
├── ViewModels/
│   └── RegisterViewModel.cs
├── Views/
│   └── Home/
│       └── Index.cshtml
├── Resources/
│   ├── Controllers/
│   │   ├── HomeController.en.resx     # English (source)
│   │   ├── HomeController.de.resx     # German
│   │   └── HomeController.ja.resx     # Japanese
│   ├── ViewModels/
│   │   ├── RegisterViewModel.en.resx
│   │   └── RegisterViewModel.de.resx
│   ├── Views/Home/
│   │   ├── Index.en.resx
│   │   └── Index.de.resx
│   └── SharedResource.en.resx
├── SharedResource.cs                  # Marker class
├── Program.cs
└── MyAspNetApp.csproj

Experimente já o i18n Agent

Solte aqui seu arquivo de tradução

JSON, YAML, PO, XML, CSV, Markdown, Properties

ou clique para selecionar

Idiomas de destino

Sem cadastroEstimativa imediata

Fallback regional com I18nAgent.LocaleChain

Quando falta uma chave em uma localidade como pt-BR, .NET passa diretamente para a cultura invariável em vez de verificar primeiro a localidade principal pt.

Terminal
dotnet add package I18nAgent.LocaleChain
Configuration
using I18nAgent.LocaleChain;

LocaleChain.Configure(new Dictionary<string, string[]>
{
    ["pt-BR"] = new[] {"pt", "en"},
    ["zh-Hant-HK"] = new[] {"zh-Hant", "zh", "en"},
});

Consulte nosso guia de fallback regional para ver a lista completa de frameworks compatíveis e as 75 cadeias integradas. Learn more →

Perguntas frequentes