
Le guide complet de la localisation avec ASP.NET Core
De IStringLocalizer à la production : configurez une localisation basée sur des ressources dans ASP.NET Core, puis automatisez les traductions grâce à l'IA.
Activer les services de localisation
Enregistrez les services de localisation dans Program.cs avec AddLocalization(), configurez les cultures prises en charge, puis ajoutez le middleware de localisation des requêtes. Cela met en place l'ensemble du pipeline de localisation de votre application ASP.NET Core.
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();Créer les fichiers de ressources RESX
ASP.NET Core utilise des fichiers RESX (ressources XML) pour les traductions. Créez un fichier par culture et par classe : HomeController.en.resx, HomeController.de.resx, etc. Le framework résout le fichier approprié en fonction de la culture de la requête en cours.
<!-- 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><!-- 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 { }Utiliser IStringLocalizer dans les contrôleurs et les services
Injectez IStringLocalizer<T> dans n'importe quel contrôleur, service ou middleware via l'injection de dépendances. Le paramètre de type générique T détermine quel fichier RESX charger. Utilisez la syntaxe entre crochets localizer["Key"] pour récupérer les chaînes traduites, avec des paramètres de format facultatifs.
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 });
}
}Configurer le middleware de culture de requête
ASP.NET Core détermine la culture de la requête à l'aide d'une chaîne de fournisseurs : chaîne de requête, cookie et en-tête Accept-Language (dans cet ordre). Vous pouvez ajouter des fournisseurs personnalisés — par exemple, pour lire la culture à partir d'un segment de route d'URL comme /de/home.
// 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: 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);
}Localiser les annotations de données
Les attributs de validation comme [Required], [StringLength] et [Display] peuvent être localisés en définissant leurs propriétés ErrorMessage ou Name sur des noms de clés RESX. Appelez AddDataAnnotationsLocalization() dans Program.cs pour activer cette fonctionnalité.
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"Gérer les pluriels et les messages ICU
.NET ne dispose pas d'une prise en charge intégrée des règles de pluriel comme ICU. Pour les cas simples, utilisez des clés RESX distinctes (ItemCount_One, ItemCount_Other) avec un switch dans le code. Pour une prise en charge complète du format ICU MessageFormat sur toutes les catégories de pluriel CLDR, utilisez la bibliothèque MessageFormat.NET.
// 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 {# عنصر}}";Localiser les vues Razor
Utilisez IViewLocalizer dans les vues Razor via @inject. Il résout les fichiers RESX en fonction du chemin de la vue. Pour des chaînes sûres en HTML avec du balisage, utilisez IHtmlLocalizer. Les Tag Helpers comme asp-for et asp-validation-for utilisent automatiquement les attributs Display et ErrorMessage localisés.
@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>Automatiser les traductions RESX
Une fois votre configuration de localisation terminée, traduisez vos fichiers RESX à l'aide de l'IA. Dans votre IDE, demandez à votre assistant IA de traduire votre RESX source, ou utilisez le CLI i18n Agent dans votre pipeline CI/CD pour maintenir les traductions synchronisées.
# 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,esAutomatiser la qualité des traductions
Corriger le repli de locale avec LocaleChain.NET
La hiérarchie CultureInfo.Parent intégrée à .NET utilise uniquement une troncature BCP 47 : pt-BR se replie sur pt puis sur InvariantCulture, en sautant pt-PT. LocaleChain.NET fournit des chaînes de repli configurables par locale pour l'ensemble de l'écosystème .NET.
dotnet add package I18nAgent.LocaleChainusing 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<>));Pièges courants
Culture non définie sur la requête
Fichier RESX introuvable
Ordre des middlewares incorrect
Le thread d'arrière-plan utilise la mauvaise culture
Structure de projet recommandée
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.csprojEssayez i18n Agent maintenant
Déposez votre fichier de traduction ici
JSON, YAML, PO, XML, CSV, Markdown, Properties
ou cliquez pour parcourir
Langues cibles
Repli de locale avec I18nAgent.LocaleChain
Lorsqu'une clé de traduction est manquante dans une locale régionale comme pt-BR, .NET passe directement à la culture invariante au lieu de vérifier d'abord la locale parente pt.
dotnet add package I18nAgent.LocaleChainusing I18nAgent.LocaleChain;
LocaleChain.Configure(new Dictionary<string, string[]>
{
["pt-BR"] = new[] {"pt", "en"},
["zh-Hant-HK"] = new[] {"zh-Hant", "zh", "en"},
});Consultez notre guide de repli de locale pour découvrir la liste complète des frameworks pris en charge et les 75 chaînes intégrées. Learn more →