Skip to main content

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.

1

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.

AddLocalization() enregistre IStringLocalizer et IStringLocalizerFactory dans le conteneur d'injection de dépendances. ResourcesPath indique au framework où trouver vos fichiers .resx. AddViewLocalization() active IViewLocalizer dans les vues Razor, et AddDataAnnotationsLocalization() active les messages de validation localisés.
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

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.{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>
Utilisez une classe SharedResource avec ses propres fichiers RESX pour les chaînes partagées entre contrôleurs et vues — libellés de boutons, éléments de navigation et messages de validation courants. Cela évite de dupliquer des clés dans des dizaines de fichiers RESX spécifiques à chaque contrôleur.
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

Utiliser IStringLocalizer dans les contrôleurs et les services

Injectez IStringLocalizer&lt;T&gt; 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.

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 renvoie le nom de la clé au lieu de la valeur traduite, vérifiez trois points : (1) le nommage du fichier RESX correspond à l'espace de noms de la classe, (2) ResourcesPath dans AddLocalization() pointe vers le bon dossier, et (3) l'action de génération (Build Action) du fichier RESX est définie sur Embedded Resource dans Visual Studio.
4

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.

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);
}
L'ordre des middlewares compte. UseRequestLocalization() doit être appelé après UseRouting() mais avant UseEndpoints() ou MapControllers(). S'il est placé trop tard, la culture ne sera pas définie lorsque vos contrôleurs s'exécuteront.
5

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é.

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 localisation des annotations de données utilise le nom de la classe ViewModel pour trouver les fichiers RESX, et non celui du contrôleur. Pour RegisterViewModel, le framework recherche Resources/ViewModels/RegisterViewModel.de.resx. Si vos fichiers RESX portent le nom du contrôleur, les messages de validation ne seront pas localisés.
6

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.

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 {# عنصر}}";
N'utilisez jamais count == 1 pour détecter les formes singulières. Le français traite 0 comme un singulier. Le russe possède des formes distinctes pour « few » et « many ». L'arabe a six catégories de pluriel. Utilisez des règles de pluriel compatibles CLDR ou une bibliothèque comme MessageFormat.NET qui gère cela correctement.
7

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.

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 résout les fichiers RESX en fonction du chemin de la vue : Views/Home/Index.cshtml recherche Resources/Views/Home/Index.de.resx. Si vous souhaitez partager des chaînes entre plusieurs vues, injectez séparément IStringLocalizer&lt;SharedResource&gt;.
8

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.

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
Traduisez de manière incrémentale — lorsque vous ajoutez de nouvelles clés à votre fichier RESX source, traduisez uniquement le diff plutôt que de régénérer tous les fichiers. Cela préserve les traductions relues par des humains et réduit les changements inutiles.

Automatiser la qualité des traductions

Détectez les clés manquantes et les espaces réservés cassés avant leur mise en production grâce à i18n-validate. Testez votre interface avec des pseudo-traductions grâce à i18n-pseudo avant l'arrivée des traductions réelles.

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.

Sans LocaleChain.NET, un utilisateur pt-BR voit l'anglais lorsqu'une chaîne en portugais est manquante — même si vous disposez d'une traduction pt-PT complète. Le même problème touche es-MX (qui saute es-419), zh-Hant (qui saute zh-Hans) et des dizaines d'autres variantes régionales.
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<>));

Pièges courants

Culture non définie sur la requête

Les traductions affichent toujours la langue par défaut. Vérifiez que UseRequestLocalization() est appelé dans le pipeline de middlewares et que les fournisseurs de culture sont configurés. Vérifiez que le navigateur envoie des en-têtes Accept-Language. Testez avec ?culture=de dans la chaîne de requête pour confirmer que le middleware fonctionne.

Fichier RESX introuvable

IStringLocalizer renvoie le nom de la clé au lieu de la valeur traduite. La cause la plus fréquente est un nom de fichier RESX qui ne correspond pas au chemin d'espace de noms complet de la classe par rapport à ResourcesPath. Activez la journalisation de débogage pour Microsoft.Extensions.Localization afin de voir les chemins que le framework recherche.

Ordre des middlewares incorrect

UseRequestLocalization() doit apparaître avant UseEndpoints() et MapControllers(). Si placé après, la culture de la requête n'est pas définie lors de l'exécution des contrôleurs. Dans l'hébergement minimal .NET 6+, appelez-le avant app.MapControllers().

Le thread d'arrière-plan utilise la mauvaise culture

CultureInfo.CurrentCulture et CurrentUICulture sont définis par thread. Les tâches d'arrière-plan (Task.Run, services hébergés) héritent de la culture du pool de threads, et non de la culture de la requête. Capturez et définissez explicitement la culture lors du lancement d'un traitement en arrière-plan.

Structure de projet recommandée

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

Essayez i18n Agent maintenant

Déposez votre fichier de traduction ici

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

ou cliquez pour parcourir

Langues cibles

Aucune inscription requiseEstimation instantanée

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.

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"},
});

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 →

Questions fréquentes