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

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

AddLocalization() regista IStringLocalizer e IStringLocalizerFactory no contentor de DI. ResourcesPath indica ao framework onde encontrar os ficheiros .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 ficheiros de recursos RESX

ASP.NET Core utiliza ficheiros RESX —recursos XML— nas traduções. Crie um ficheiro por cultura e classe: HomeController.en.resx, HomeController.de.resx, etc. O framework resolve o ficheiro correto segundo a cultura do pedido 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 os seus ficheiros RESX para cadeias partilhadas 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 ficheiros 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 cadeias 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 dos pedidos

ASP.NET Core determina a cultura através de uma cadeia de fornecedores: cadeia de consulta, cookie e cabeçalho Accept-Language, por esta ordem. Pode adicionar fornecedores 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() tem de 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 ficheiros 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 detetar 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 ficheiro da vista. Em cadeias 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 partilhar cadeias 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 ficheiros. Assim preserva traduções revistas por pessoas e minimiza alterações desnecessárias.

Automatizar a qualidade das traduções

Detete 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 recurso 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 região para todo o ecossistema .NET.

Sem LocaleChain.NET, um utilizador pt-BR vê inglês quando falta uma cadeia 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 no pedido

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.

Ficheiro 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 registo de depuração de Microsoft.Extensions.Localization para ver os caminhos procurados.

Ordem incorreta do middleware

UseRequestLocalization() tem de 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 do pedido. 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

Largue aqui o seu ficheiro de tradução

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

ou clique para selecionar

Idiomas de destino

Sem registoEstimativa imediata

Recurso regional com I18nAgent.LocaleChain

Quando falta uma chave numa região como pt-BR, .NET passa diretamente para a cultura invariável em vez de verificar primeiro a região 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 o nosso guia de recurso regional para ver a lista completa de frameworks compatíveis e as 75 cadeias integradas. Learn more →

Perguntas frequentes