Skip to main content

Пълното ръководство за локализация на ASP.NET Core

От IStringLocalizer до производствена среда: конфигурирайте локализация с ресурси в ASP.NET Core, след което автоматизирайте преводите с ИИ.

1

Активирайте услугите за локализация

Регистрирайте услугите за локализация в Program.cs чрез AddLocalization(), конфигурирайте поддържаните култури и добавете междинния компонент за локализация на заявките. Така активирате целия процес на локализация за Вашето ASP.NET Core приложение.

AddLocalization() регистрира IStringLocalizer и IStringLocalizerFactory в DI контейнера. ResourcesPath указва на платформата къде да намира Вашите .resx файлове. AddViewLocalization() активира IViewLocalizer в изгледите на Razor, а AddDataAnnotationsLocalization() активира локализираните съобщения за проверка.
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

Създайте ресурсни RESX файлове

ASP.NET Core използва RESX (XML ресурсни) файлове за преводите. Създайте по един файл за всяка култура и всеки клас: HomeController.en.resx, HomeController.de.resx и т.н. Платформата избира правилния файл според културата на текущата заявка.

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>
Използвайте клас SharedResource със собствени RESX файлове за низовете, споделени между контролери и изгледи — етикети на бутони, елементи за навигация и общи съобщения за проверка. Така няма да дублирате ключове в десетки RESX файлове за отделни контролери.
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

Използвайте IStringLocalizer в контролери и услуги

Внедрете IStringLocalizer&lt;T&gt; във всеки контролер, услуга или междинен компонент чрез внедряване на зависимости. Параметърът на обобщения тип T определя кой RESX файл да се зареди. Използвайте синтаксиса със скоби localizer["Key"], за да извлечете преведените низове с незадължителни параметри за форматиране.

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 });
    }
}
Ако IStringLocalizer връща името на ключа вместо преведената стойност, проверете три неща: (1) дали името на RESX файла съответства на пространството от имена на класа, (2) дали ResourcesPath в AddLocalization() сочи към правилната папка и (3) дали Build Action на RESX файла е зададено на Embedded Resource във Visual Studio.
4

Конфигурирайте междинния компонент за културата на заявката

ASP.NET Core определя културата на заявката чрез верига от доставчици: параметър в заявката, бисквитка и заглавка Accept-Language (в този ред). Можете да добавите собствени доставчици — например такъв, който прочита културата от сегмент на URL маршрута като /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);
}
Редът на междинните компоненти е от значение. UseRequestLocalization() трябва да бъде извикан след UseRouting(), но преди UseEndpoints() или MapControllers(). Ако е поставен твърде късно, културата няма да бъде зададена при изпълнението на контролерите.
5

Локализирайте анотациите на данни

Атрибутите за проверка като [Required], [StringLength] и [Display] могат да бъдат локализирани, като зададете техните свойства ErrorMessage или Name на имена на RESX ключове. Извикайте AddDataAnnotationsLocalization() в Program.cs, за да активирате тази възможност.

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"
При локализиране на анотации на данни RESX файловете се търсят по името на класа ViewModel, а не на Controller. За RegisterViewModel платформата търси Resources/ViewModels/RegisterViewModel.de.resx. Ако Вашите RESX файлове са именувани по контролера, съобщенията за проверка няма да бъдат локализирани.
6

Обработвайте множествени форми и ICU съобщения

.NET няма вградена поддръжка за правила за множествено число като ICU. В простите случаи използвайте отделни RESX ключове (ItemCount_One, ItemCount_Other) и избор в кода. За пълна поддръжка на ICU MessageFormat във всички категории за множествено число от CLDR използвайте библиотеката 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 {# عنصر}}";
Никога не използвайте count == 1 за определяне на единственото число. Френският третира 0 като единствено число. Руският има отделни форми за 'few' и 'many'. Арабският има шест категории за множествено число. Използвайте правила, съобразени с CLDR, или библиотека като MessageFormat.NET, която ги обработва правилно.
7

Локализирайте изгледите на Razor

Използвайте IViewLocalizer в изгледите на Razor чрез @inject. Той намира RESX файловете според пътя до файла на изгледа. За низове с HTML маркировка, която не трябва да се кодира, използвайте IHtmlLocalizer. Tag Helpers като asp-for и asp-validation-for автоматично използват локализираните атрибути Display и 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 намира RESX файловете според пътя до изгледа: за Views/Home/Index.cshtml се търси Resources/Views/Home/Index.de.resx. Ако искате да споделяте низове между изгледи, внедрете отделно IStringLocalizer&lt;SharedResource&gt;.
8

Автоматизирайте превода на RESX файловете

След като конфигурирате локализацията, преведете своите RESX файлове с помощта на ИИ. Поискайте от асистента с ИИ във Вашата IDE да преведе изходния RESX файл или използвайте i18n Agent CLI във Вашия CI/CD пайплайн, за да поддържате преводите синхронизирани.

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
Превеждайте поетапно — когато добавяте нови ключове в изходния RESX файл, превеждайте само разликите, вместо да създавате всички файлове отначало. Така запазвате преводите, които вече са прегледани от човек, и свеждате ненужните промени до минимум.

Автоматизирайте контрола на качеството на превода

Откривайте липсващи ключове и повредени запазени места с i18n-validate, преди да достигнат до потребителите. Тествайте интерфейса си с псевдопреводи чрез i18n-pseudo, преди да получите истинските преводи.

Коригирайте резервните локали с LocaleChain.NET

Вградената йерархия CultureInfo.Parent на .NET използва само съкращаване по BCP 47: pt-BR преминава към pt, а след това към InvariantCulture, като пропуска pt-PT. LocaleChain.NET предоставя конфигурируеми вериги от резервни локали за цялата екосистема на .NET.

Без LocaleChain.NET потребител с pt-BR вижда английски, когато липсва португалски низ — дори ако разполагате с пълен превод на pt-PT. Същият проблем засяга es-MX (пропуска es-419), zh-Hant (пропуска zh-Hans) и десетки други регионални варианти.
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<>));

Често срещани затруднения

Културата не е зададена за заявката

Преводите винаги се показват на езика по подразбиране. Проверете дали UseRequestLocalization() е извикан в пайплайна на междинните компоненти и дали доставчиците на култура са конфигурирани. Уверете се, че браузърът изпраща заглавки Accept-Language. Направете тест с ?culture=de в низа на заявката, за да потвърдите, че междинният компонент работи.

RESX файлът не е намерен

IStringLocalizer връща името на ключа вместо преведената стойност. Най-честата причина е име на RESX файл, което не съответства на пълния път на пространството от имена на класа спрямо ResourcesPath. Активирайте регистрирането на съобщения за отстраняване на грешки за Microsoft.Extensions.Localization, за да видите в кои пътища търси платформата.

Неправилен ред на междинните компоненти

UseRequestLocalization() трябва да се намира преди UseEndpoints() и MapControllers(). Ако е поставен след тях, културата на заявката не е зададена при изпълнението на контролерите. При минималния модел на хостване в .NET 6+ го извикайте преди app.MapControllers().

Фоновата нишка използва грешна култура

CultureInfo.CurrentCulture и CurrentUICulture са специфични за всяка нишка. Фоновите задачи (Task.Run, хоствани услуги) наследяват културата на нишката от пула, а не културата на заявката. Запазвайте и задавайте изрично културата при възлагане на фонова работа.

Препоръчителна структура на проекта

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

Изпробвайте i18n Agent сега

Пуснете тук Вашия файл за превод

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

или натиснете, за да изберете файл

Целеви езици

Не се изисква регистрацияНезабавна оценка

Резервни локали с I18nAgent.LocaleChain

Когато ключ за превод липсва в регионален локал като pt-BR, .NET преминава направо към инвариантната култура, вместо първо да провери родителския локал 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"},
});

Вижте нашето ръководство за резервна локализация за пълния списък с поддържани платформи и 75 вградени вериги. Learn more →

Често задавани въпроси