Skip to main content

Kompletny przewodnik po lokalizacji ASP.NET Core

Od IStringLocalizer po produkcję: skonfiguruj lokalizację z wykorzystaniem zasobów w ASP.NET Core, a następnie zautomatyzuj tłumaczenia za pomocą AI.

1

Włącz usługi lokalizacyjne

Zarejestruj usługi lokalizacyjne w Program.cs za pomocą AddLocalization(), skonfiguruj obsługiwane kultury i dodaj middleware lokalizacji żądania. Łączy to cały pipeline lokalizacyjny aplikacji ASP.NET Core.

AddLocalization() rejestruje IStringLocalizer oraz IStringLocalizerFactory w kontenerze DI. ResourcesPath wskazuje frameworkowi miejsce plików .resx. AddViewLocalization() włącza IViewLocalizer w widokach Razor, a AddDataAnnotationsLocalization() włącza zlokalizowane komunikaty walidacji.
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

Utwórz pliki zasobów RESX

ASP.NET Core używa do tłumaczeń plików RESX (zasobów XML). Utwórz po jednym pliku dla każdej kultury i klasy: HomeController.en.resx, HomeController.de.resx itd. Framework rozwiązuje właściwy plik na podstawie bieżącej kultury żądania.

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>
Użyj klasy SharedResource z własnymi plikami RESX dla tekstów wspólnych dla kontrolerów i widoków — etykiet przycisków, elementów nawigacji oraz typowych komunikatów walidacji. Pozwala to uniknąć duplikowania kluczy w dziesiątkach plików RESX właściwych dla kontrolerów.
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

Używaj IStringLocalizer w kontrolerach i usługach

Wstrzyknij IStringLocalizer&lt;T&gt; do dowolnego kontrolera, usługi lub middleware przez wstrzykiwanie zależności. Ogólny parametr typu T określa wczytywany plik RESX. Używaj składni nawiasów localizer["Key"] do pobierania przetłumaczonych tekstów z opcjonalnymi parametrami formatowania.

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 });
    }
}
Jeśli IStringLocalizer zwraca nazwę klucza zamiast przetłumaczonej wartości, sprawdź trzy rzeczy: (1) czy nazwa pliku RESX odpowiada przestrzeni nazw klasy, (2) czy ResourcesPath w AddLocalization() wskazuje właściwy folder oraz (3) czy Build Action pliku RESX ustawiono w Visual Studio na Embedded Resource.
4

Skonfiguruj middleware kultury żądania

ASP.NET Core określa kulturę żądania za pomocą łańcucha dostawców: parametrów zapytania, pliku cookie i nagłówka Accept-Language (w tej kolejności). Możesz dodać własnych dostawców — na przykład odczytujących kulturę z segmentu trasy URL takiego jak /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);
}
Kolejność middleware ma znaczenie. UseRequestLocalization() trzeba wywołać po UseRouting(), ale przed UseEndpoints() lub MapControllers(). Jeśli znajdzie się zbyt późno, kultura nie zostanie ustawiona przed wykonaniem kontrolerów.
5

Lokalizuj adnotacje danych

Atrybuty walidacji takie jak [Required], [StringLength] i [Display] można lokalizować, ustawiając ich właściwości ErrorMessage lub Name na nazwy kluczy RESX. Wywołaj AddDataAnnotationsLocalization() w Program.cs, aby to włączyć.

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"
Lokalizacja adnotacji danych używa nazwy klasy ViewModel do znajdowania plików RESX, a nie nazwy kontrolera. Dla RegisterViewModel framework szuka Resources/ViewModels/RegisterViewModel.de.resx. Jeśli pliki RESX nazwano według kontrolera, komunikaty walidacji nie zostaną zlokalizowane.
6

Obsłuż liczbę mnogą i wiadomości ICU

.NET nie ma wbudowanej obsługi reguł liczby mnogiej takiej jak ICU. W prostych przypadkach użyj osobnych kluczy RESX (ItemCount_One, ItemCount_Other) z przełącznikiem w kodzie. Pełną obsługę ICU MessageFormat dla wszystkich kategorii liczby mnogiej CLDR zapewnia biblioteka 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 {# عنصر}}";
Nigdy nie używaj count == 1 do wykrywania liczby pojedynczej. Francuski traktuje 0 jak liczbę pojedynczą. Rosyjski ma osobne formy 'few' i 'many'. Arabski ma sześć kategorii liczby mnogiej. Używaj reguł zgodnych z CLDR lub biblioteki takiej jak MessageFormat.NET, która obsługuje je poprawnie.
7

Lokalizuj widoki Razor

Używaj IViewLocalizer w widokach Razor przez @inject. Rozwiązuje pliki RESX na podstawie ścieżki pliku widoku. Dla tekstów zawierających znaczniki i bezpiecznych dla HTML używaj IHtmlLocalizer. Tag Helpers takie jak asp-for i asp-validation-for automatycznie korzystają ze zlokalizowanych atrybutów Display oraz ErrorMessage.

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 rozwiązuje pliki RESX według ścieżki widoku: dla Views/Home/Index.cshtml szuka Resources/Views/Home/Index.de.resx. Jeśli chcesz współdzielić teksty między widokami, osobno wstrzyknij IStringLocalizer&lt;SharedResource&gt;.
8

Zautomatyzuj tłumaczenia RESX

Po skonfigurowaniu lokalizacji tłumacz pliki RESX za pomocą AI. Poproś asystenta AI w środowisku programistycznym o przetłumaczenie źródłowego pliku RESX albo użyj CLI i18n Agent w pipeline CI/CD, aby utrzymywać synchronizację tłumaczeń.

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
Tłumacz przyrostowo — po dodaniu nowych kluczy do źródłowego pliku RESX przetłumacz tylko różnicę zamiast ponownie generować wszystkie pliki. Pozwala to zachować tłumaczenia sprawdzone przez człowieka i ograniczyć niepotrzebne zmiany.

Zautomatyzuj kontrolę jakości tłumaczeń

Wykrywaj brakujące klucze i uszkodzone symbole zastępcze przed wydaniem za pomocą i18n-validate. Testuj interfejs z pseudotłumaczeniami przy użyciu i18n-pseudo, zanim pojawią się prawdziwe tłumaczenia.

Napraw języki rezerwowe za pomocą LocaleChain.NET

Wbudowana hierarchia CultureInfo.Parent platformy .NET używa tylko skracania BCP 47: pt-BR wraca do pt, a następnie InvariantCulture, pomijając pt-PT. LocaleChain.NET zapewnia konfigurowalne łańcuchy rezerwowe dla każdej kultury w całym ekosystemie .NET.

Bez LocaleChain.NET użytkownik pt-BR widzi angielski, gdy brakuje portugalskiego tekstu — nawet jeśli dostępne jest kompletne tłumaczenie pt-PT. Ten sam problem dotyczy es-MX (pomija es-419), zh-Hant (pomija zh-Hans) i dziesiątek innych wariantów regionalnych.
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<>));

Typowe pułapki

Kultura nie jest ustawiona dla żądania

Tłumaczenia zawsze wyświetlają język domyślny. Sprawdź, czy UseRequestLocalization() jest wywoływane w pipeline middleware i czy skonfigurowano dostawców kultury. Upewnij się, że przeglądarka wysyła nagłówki Accept-Language. Przetestuj parametr ?culture=de w zapytaniu, aby potwierdzić działanie middleware.

Nie znaleziono pliku RESX

IStringLocalizer zwraca nazwę klucza zamiast przetłumaczonej wartości. Najczęstszą przyczyną jest nazwa pliku RESX, która nie odpowiada pełnej ścieżce przestrzeni nazw klasy względem ResourcesPath. Włącz dzienniki debugowania Microsoft.Extensions.Localization, aby zobaczyć ścieżki przeszukiwane przez framework.

Nieprawidłowa kolejność middleware

UseRequestLocalization() musi znajdować się przed UseEndpoints() i MapControllers(). Jeśli jest później, kultura żądania nie zostanie ustawiona przed wykonaniem kontrolerów. W minimalnym modelu hostowania .NET 6+ wywołaj go przed app.MapControllers().

Wątek w tle używa niewłaściwej kultury

CultureInfo.CurrentCulture oraz CurrentUICulture są właściwe dla każdego wątku. Zadania w tle (Task.Run, usługi hostowane) dziedziczą kulturę puli wątków, a nie żądania. Podczas zlecania pracy w tle jawnie przechwyć i ustaw kulturę.

Zalecana struktura projektu

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

Wypróbuj i18n Agent

Upuść tutaj plik tłumaczenia

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

lub kliknij, aby go wybrać

Języki docelowe

Rejestracja nie jest wymaganaNatychmiastowa wycena

Rezerwowe ustawienia regionalne z I18nAgent.LocaleChain

Gdy brakuje klucza tłumaczenia w regionalnej kulturze takiej jak pt-BR, .NET przechodzi bezpośrednio do kultury niezmiennej, zamiast najpierw sprawdzić kulturę nadrzędną 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"},
});

Zobacz nasz przewodnik po rezerwowych ustawieniach regionalnych, aby poznać pełną listę obsługiwanych frameworków i 75 wbudowanych łańcuchów. Learn more →

Najczęściej zadawane pytania