Skip to main content

Guía completa de localización para ASP.NET Core

De IStringLocalizer a producción: configure la localización basada en recursos de ASP.NET Core y automatice después las traducciones con IA.

1

Activar los servicios de localización

Registre los servicios en Program.cs mediante AddLocalization(), configure las culturas admitidas y añada el middleware de localización de solicitudes. Así conecta todo el proceso de localización de su aplicación ASP.NET Core.

AddLocalization() registra IStringLocalizer e IStringLocalizerFactory en el contenedor de inyección de dependencias. ResourcesPath indica dónde encontrar los archivos .resx. AddViewLocalization() activa IViewLocalizer en vistas Razor y AddDataAnnotationsLocalization() habilita mensajes de validación localizados.
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

Crear archivos de recursos RESX

ASP.NET Core utiliza archivos RESX —recursos XML— para las traducciones. Cree uno por cultura y clase: HomeController.en.resx, HomeController.de.resx, etc. El framework resuelve el correcto según la cultura de la solicitud actual.

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>
Utilice una clase SharedResource con sus propios RESX para cadenas compartidas entre controladores y vistas: etiquetas de botones, elementos de navegación y mensajes de validación comunes. Así evita duplicar claves en decenas de archivos 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 en controladores y servicios

Inyecte IStringLocalizer&lt;T&gt; en cualquier controlador, servicio o middleware. El parámetro genérico T determina qué RESX cargar. Utilice la sintaxis de corchetes localizer["Key"] para obtener cadenas traducidas, con parámetros de formato opcionales.

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 });
    }
}
Si IStringLocalizer devuelve el nombre de la clave, compruebe tres cosas: 1) que el nombre del RESX coincida con el espacio de nombres de la clase; 2) que ResourcesPath en AddLocalization() apunte a la carpeta correcta; 3) que Build Action del RESX esté definido como Embedded Resource en Visual Studio.
4

Configurar el middleware de cultura de solicitudes

ASP.NET Core determina la cultura mediante una cadena de proveedores: cadena de consulta, cookie y cabecera Accept-Language, por ese orden. Puede añadir proveedores personalizados; por ejemplo, leerla de un segmento de ruta 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);
}
El orden del middleware importa. Debe llamar a UseRequestLocalization() después de UseRouting(), pero antes de UseEndpoints() o MapControllers(). Si aparece demasiado tarde, la cultura no estará definida al ejecutar los controladores.
5

Localizar anotaciones de datos

Puede localizar atributos de validación como [Required], [StringLength] y [Display] definiendo sus propiedades ErrorMessage o Name con claves RESX. Llame a AddDataAnnotationsLocalization() en Program.cs para activarlo.

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"
La localización de anotaciones de datos utiliza el nombre de la clase ViewModel para buscar RESX, no el controlador. Para RegisterViewModel, busca Resources/ViewModels/RegisterViewModel.de.resx. Si sus archivos tienen el nombre del controlador, los mensajes no se localizarán.
6

Gestionar plurales y mensajes ICU

.NET no admite reglas de plural de forma nativa como ICU. En casos sencillos, utilice claves RESX distintas —ItemCount_One, ItemCount_Other— con una selección en el código. Para ICU MessageFormat completo en todas las categorías CLDR, use 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 utilice count == 1 para detectar singulares. El francés considera singular el 0, el ruso tiene formas distintas para «few» y «many», y el árabe seis categorías. Utilice reglas conscientes de CLDR o una biblioteca como MessageFormat.NET.
7

Localizar vistas Razor

Utilice IViewLocalizer en vistas Razor mediante @inject. Resuelve RESX según la ruta del archivo de vista. Para cadenas con marcado seguro, utilice IHtmlLocalizer. Los Tag Helpers como asp-for y asp-validation-for emplean automáticamente atributos Display y 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 resuelve RESX por la ruta de la vista: Views/Home/Index.cshtml busca Resources/Views/Home/Index.de.resx. Para compartir cadenas entre vistas, inyecte por separado IStringLocalizer&lt;SharedResource&gt;.
8

Automatizar traducciones RESX

Cuando complete la configuración, traduzca los RESX con IA. Desde el IDE, pida a su asistente que traduzca el RESX de origen o utilice la CLI de i18n Agent en CI/CD para mantener sincronizadas las traducciones.

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
Traduzca de forma incremental: cuando añada claves nuevas al RESX de origen, traduzca solo las diferencias en vez de volver a generar todos los archivos. Así conserva las traducciones revisadas por personas y reduce los cambios.

Automatizar la calidad de la traducción

Detecte las claves ausentes y los marcadores rotos antes de publicar con i18n-validate. Pruebe la interfaz con pseudotraducciones mediante i18n-pseudo antes de recibir las reales.

Corregir el respaldo regional con LocaleChain.NET

La jerarquía CultureInfo.Parent integrada en .NET solo trunca BCP 47: pt-BR recurre a pt y después InvariantCulture, y omite pt-PT. LocaleChain.NET ofrece cadenas configurables por región para todo el ecosistema .NET.

Sin LocaleChain.NET, un usuario pt-BR ve inglés cuando falta una cadena portuguesa, aunque exista una traducción pt-PT completa. El mismo problema afecta a es-MX —omite es-419—, zh-Hant —omite zh-Hans— y decenas de variantes regionales.
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<>));

Errores habituales

La cultura no está definida en la solicitud

Las traducciones muestran siempre el idioma predeterminado. Compruebe que se llame a UseRequestLocalization() en el middleware y que los proveedores estén configurados. Verifique que el navegador envíe Accept-Language. Pruebe con ?culture=de en la consulta para confirmar que funciona.

No se encuentra el archivo RESX

IStringLocalizer devuelve el nombre de la clave. La causa más habitual es que el nombre del RESX no coincida con la ruta completa del espacio de nombres de la clase respecto a ResourcesPath. Active el registro de depuración de Microsoft.Extensions.Localization para ver dónde busca el framework.

Orden incorrecto del middleware

UseRequestLocalization() debe aparecer antes de UseEndpoints() y MapControllers(). Si está después, la cultura no se define al ejecutar los controladores. En el alojamiento mínimo de .NET 6 o posterior, llámelo antes de app.MapControllers().

El hilo en segundo plano utiliza la cultura equivocada

CultureInfo.CurrentCulture y CurrentUICulture pertenecen a cada hilo. Las tareas en segundo plano —Task.Run o servicios alojados— heredan la cultura del grupo de hilos, no la de la solicitud. Capture y defina expresamente la cultura al enviar trabajo en segundo plano.

Estructura de proyecto 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

Pruebe i18n Agent ahora

Arrastre y suelte aquí su archivo de traducción

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

o haga clic para seleccionar

Idiomas de destino

No es necesario registrarsePresupuesto al instante

Respaldo de configuraciones regionales con I18nAgent.LocaleChain

Cuando falta una clave en una configuración regional como pt-BR, .NET pasa directamente a la cultura invariable en vez de comprobar primero la 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 nuestra guía de respaldo de configuraciones regionales para ver todos los frameworks admitidos y las 75 cadenas integradas. Learn more →

Preguntas frecuentes