Skip to main content

Ο πλήρης οδηγός τοπικής προσαρμογής του ASP.NET Core

Από το IStringLocalizer έως την παραγωγή: ρυθμίστε τοπική προσαρμογή με πόρους στο ASP.NET Core και αυτοματοποιήστε τις μεταφράσεις με AI.

1

Ενεργοποιήστε τις υπηρεσίες τοπικής προσαρμογής

Καταχωρίστε τις υπηρεσίες τοπικής προσαρμογής στο Program.cs με το AddLocalization(), διαμορφώστε τις υποστηριζόμενες γλώσσες και προσθέστε το middleware τοπικής προσαρμογής αιτημάτων. Έτσι ενεργοποιείται ολόκληρη η διοχέτευση τοπικής προσαρμογής για την εφαρμογή ASP.NET Core.

Το AddLocalization() καταχωρίζει τα IStringLocalizer και IStringLocalizerFactory στο κοντέινερ DI. Το ResourcesPath υποδεικνύει στο framework πού θα βρει τα αρχεία .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 κ.λπ. Το framework επιλέγει το σωστό αρχείο βάσει της τρέχουσας γλώσσας του αιτήματος.

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 για συμβολοσειρές που χρησιμοποιούνται από πολλούς controllers και προβολές — ετικέτες κουμπιών, στοιχεία πλοήγησης και κοινά μηνύματα επικύρωσης. Έτσι αποφεύγετε τη δημιουργία διπλότυπων κλειδιών σε δεκάδες αρχεία RESX που αφορούν συγκεκριμένους controllers.
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 σε controllers και υπηρεσίες

Εισαγάγετε το IStringLocalizer&lt;T&gt; σε οποιονδήποτε controller, υπηρεσία ή middleware μέσω dependency injection. Η γενική παράμετρος τύπου 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 αντιστοιχεί στο namespace της κλάσης, (2) ότι το ResourcesPath στο AddLocalization() δείχνει στον σωστό φάκελο και (3) ότι το Build Action του αρχείου RESX έχει οριστεί σε Embedded Resource στο Visual Studio.
4

Διαμορφώστε το middleware γλώσσας αιτήματος

Το ASP.NET Core προσδιορίζει τη γλώσσα του αιτήματος μέσω μιας ακολουθίας παρόχων: συμβολοσειρά ερωτήματος, cookie και κεφαλίδα 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);
}
Η σειρά των middleware έχει σημασία. Το UseRequestLocalization() πρέπει να καλείται μετά το UseRouting(), αλλά πριν από το UseEndpoints() ή το MapControllers(). Αν τοποθετηθεί πολύ αργά, η γλώσσα δεν θα έχει οριστεί όταν εκτελεστούν οι controllers.
5

Μεταφράστε τα data annotations

Χαρακτηριστικά επικύρωσης όπως τα [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"
Η τοπική προσαρμογή των data annotations χρησιμοποιεί το όνομα της κλάσης ViewModel για να εντοπίσει τα αρχεία RESX και όχι τον Controller. Για το RegisterViewModel, το framework αναζητά το Resources/ViewModels/RegisterViewModel.de.resx. Αν τα αρχεία RESX έχουν ονομαστεί βάσει του controller, τα μηνύματα επικύρωσης δεν θα μεταφραστούν.
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 με AI. Ζητήστε από τον βοηθό AI στο 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, μεταφράζετε μόνο τις διαφορές αντί να δημιουργείτε ξανά όλα τα αρχεία. Έτσι διατηρούνται οι μεταφράσεις που έχουν ελεγχθεί από άνθρωπο και περιορίζονται οι περιττές αλλαγές.

Αυτοματοποιήστε τον έλεγχο ποιότητας των μεταφράσεων

Εντοπίστε κλειδιά που λείπουν και λανθασμένα placeholders πριν φτάσουν στην παραγωγή με το i18n-validate. Δοκιμάστε το UI με ψευδομεταφράσεις χρησιμοποιώντας το 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() καλείται στη διοχέτευση middleware και ότι οι πάροχοι γλώσσας έχουν διαμορφωθεί. Βεβαιωθείτε ότι το πρόγραμμα περιήγησης στέλνει κεφαλίδες Accept-Language. Δοκιμάστε το ?culture=de στη συμβολοσειρά ερωτήματος για να επιβεβαιώσετε ότι το middleware λειτουργεί.

Το αρχείο RESX δεν βρέθηκε

Το IStringLocalizer επιστρέφει το όνομα του κλειδιού αντί για τη μεταφρασμένη τιμή. Η συνηθέστερη αιτία είναι ένα όνομα αρχείου RESX που δεν αντιστοιχεί στην πλήρη διαδρομή namespace της κλάσης σε σχέση με το ResourcesPath. Ενεργοποιήστε την καταγραφή εντοπισμού σφαλμάτων για το Microsoft.Extensions.Localization, ώστε να δείτε ποιες διαδρομές αναζητά το framework.

Λανθασμένη σειρά middleware

Το UseRequestLocalization() πρέπει να εμφανίζεται πριν από τα UseEndpoints() και MapControllers(). Αν τοποθετηθεί μετά, η γλώσσα του αιτήματος δεν έχει οριστεί όταν εκτελούνται οι controllers. Στο minimal hosting του .NET 6+, καλέστε το πριν από το app.MapControllers().

Το νήμα παρασκηνίου χρησιμοποιεί λανθασμένη γλώσσα

Τα CultureInfo.CurrentCulture και CurrentUICulture ορίζονται ανά νήμα. Οι εργασίες παρασκηνίου (Task.Run, hosted services) κληρονομούν τη γλώσσα του thread pool και όχι τη γλώσσα του αιτήματος. Καταγράψτε και ορίστε ρητά τη γλώσσα κατά την αποστολή εργασιών στο παρασκήνιο.

Προτεινόμενη δομή έργου

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 μεταβαίνει απευθείας στην invariant culture αντί να ελέγξει πρώτα τη γονική γλώσσα 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"},
});

Δείτε τον οδηγό μας για την εναλλακτική επιλογή γλώσσας, με την πλήρη λίστα των υποστηριζόμενων framework και 75 ενσωματωμένων αλυσίδων. Learn more →

Συχνές ερωτήσεις