asp.net-core
Lokalisierung
Suche…
Lokalisierung mit JSON-Sprachressourcen
In ASP.NET Core gibt es mehrere Möglichkeiten, wie wir unsere App lokalisieren / globalisieren können. Es ist wichtig, einen Weg zu wählen, der Ihren Bedürfnissen entspricht. In diesem Beispiel .json
Sie, wie wir eine mehrsprachige ASP.NET Core-App .json
können, die sprachspezifische Zeichenfolgen aus .json
Dateien liest und diese im Arbeitsspeicher speichert, um sie in allen Abschnitten der App zu lokalisieren und eine hohe Leistung zu gewährleisten.
Wir machen das so, indem wir das Paket Microsoft.EntityFrameworkCore.InMemory
.
Anmerkungen:
- Der Namespace für dieses Projekt ist
DigitalShop
, den Sie in den eigenen Namespace Ihres Projekts ändern können - Erwägen Sie, ein neues Projekt zu erstellen, damit Sie nicht auf seltsame Fehler stoßen
- Dieses Beispiel zeigt keinesfalls die bewährten Methoden. Wenn Sie also der Meinung sind, dass es verbessert werden kann, bearbeiten Sie es bitte
Zu Beginn fügen wir die folgenden Pakete zu den vorhandenen dependencies
in der Datei project.json
:
"Microsoft.EntityFrameworkCore": "1.0.0",
"Microsoft.EntityFrameworkCore.SqlServer": "1.0.0",
"Microsoft.EntityFrameworkCore.InMemory": "1.0.0"
Jetzt ersetzen wir die Datei Startup.cs
durch: ( using
Anweisungen werden entfernt, da sie später leicht hinzugefügt werden können)
Startup.cs
namespace DigitalShop
{
public class Startup
{
public static string UiCulture;
public static string CultureDirection;
public static IStringLocalizer _e; // This is how we access language strings
public static IConfiguration LocalConfig;
public Startup(IHostingEnvironment env)
{
var builder = new ConfigurationBuilder()
.SetBasePath(env.ContentRootPath)
.AddJsonFile("appsettings.json", optional: false, reloadOnChange: true) // this is where we store apps configuration including language
.AddJsonFile($"appsettings.{env.EnvironmentName}.json", optional: true)
.AddEnvironmentVariables();
Configuration = builder.Build();
LocalConfig = Configuration;
}
public IConfigurationRoot Configuration { get; }
// This method gets called by the runtime. Use this method to add services to the container.
public void ConfigureServices(IServiceCollection services)
{
services.AddMvc().AddViewLocalization().AddDataAnnotationsLocalization();
// IoC Container
// Add application services.
services.AddTransient<EFStringLocalizerFactory>();
services.AddSingleton<IConfiguration>(Configuration);
}
// This method gets called by the runtime. Use this method to configure the HTTP request pipeline.
public void Configure(IApplicationBuilder app, IHostingEnvironment env, ILoggerFactory loggerFactory, EFStringLocalizerFactory localizerFactory)
{
_e = localizerFactory.Create(null);
// a list of all available languages
var supportedCultures = new List<CultureInfo>
{
new CultureInfo("en-US"),
new CultureInfo("fa-IR")
};
var requestLocalizationOptions = new RequestLocalizationOptions
{
SupportedCultures = supportedCultures,
SupportedUICultures = supportedCultures,
};
requestLocalizationOptions.RequestCultureProviders.Insert(0, new JsonRequestCultureProvider());
app.UseRequestLocalization(requestLocalizationOptions);
app.UseStaticFiles();
app.UseMvc(routes =>
{
routes.MapRoute(
name: "default",
template: "{controller=Home}/{action=Index}/{id?}");
});
}
}
public class JsonRequestCultureProvider : RequestCultureProvider
{
public override Task<ProviderCultureResult> DetermineProviderCultureResult(HttpContext httpContext)
{
if (httpContext == null)
{
throw new ArgumentNullException(nameof(httpContext));
}
var config = Startup.LocalConfig;
string culture = config["AppOptions:Culture"];
string uiCulture = config["AppOptions:UICulture"];
string culturedirection = config["AppOptions:CultureDirection"];
culture = culture ?? "fa-IR"; // Use the value defined in config files or the default value
uiCulture = uiCulture ?? culture;
Startup.UiCulture = uiCulture;
culturedirection = culturedirection ?? "rlt"; // rtl is set to be the default value in case culturedirection is null
Startup.CultureDirection = culturedirection;
return Task.FromResult(new ProviderCultureResult(culture, uiCulture));
}
}
}
Im obigen Code fügen wir zuerst drei public static
Feldvariablen hinzu, die wir später mit den aus der Einstellungsdatei gelesenen Werten initialisieren.
In der Konstruktor-Klasse " Startup
fügen wir der builder
Variablen eine Json-Einstellungsdatei hinzu. Die erste Datei ist erforderlich, damit die App funktioniert. Erstellen appsettings.json
in Ihrem Projektstamm die Datei appsettings.json
falls diese noch nicht vorhanden ist. Bei Verwendung von Visual Studio 2015 wird diese Datei automatisch erstellt. Ändern Sie einfach den Inhalt in: (Sie können den Logging
auslassen, wenn Sie ihn nicht verwenden.)
appsettings.json
{
"Logging": {
"IncludeScopes": false,
"LogLevel": {
"Default": "Debug",
"System": "Information",
"Microsoft": "Information"
}
},
"AppOptions": {
"Culture": "en-US", // fa-IR for Persian
"UICulture": "en-US", // same as above
"CultureDirection": "ltr" // rtl for Persian/Arabic/Hebrew
}
}
Erstellen Sie in der Zukunft drei Ordner in Ihrem Projektstamm:
Models
, Services
und Languages
. Erstellen Sie im Ordner Models
einen anderen Ordner mit dem Namen Localization
.
Im Ordner Services
erstellen wir eine neue CS-Datei mit dem Namen EFLocalization
. Der Inhalt wäre: (Wieder using
Aussagen sind nicht enthalten)
EFLocalization.cs
namespace DigitalShop.Services
{
public class EFStringLocalizerFactory : IStringLocalizerFactory
{
private readonly LocalizationDbContext _db;
public EFStringLocalizerFactory()
{
_db = new LocalizationDbContext();
// Here we define all available languages to the app
// available languages are those that have a json and cs file in
// the Languages folder
_db.AddRange(
new Culture
{
Name = "en-US",
Resources = en_US.GetList()
},
new Culture
{
Name = "fa-IR",
Resources = fa_IR.GetList()
}
);
_db.SaveChanges();
}
public IStringLocalizer Create(Type resourceSource)
{
return new EFStringLocalizer(_db);
}
public IStringLocalizer Create(string baseName, string location)
{
return new EFStringLocalizer(_db);
}
}
public class EFStringLocalizer : IStringLocalizer
{
private readonly LocalizationDbContext _db;
public EFStringLocalizer(LocalizationDbContext db)
{
_db = db;
}
public LocalizedString this[string name]
{
get
{
var value = GetString(name);
return new LocalizedString(name, value ?? name, resourceNotFound: value == null);
}
}
public LocalizedString this[string name, params object[] arguments]
{
get
{
var format = GetString(name);
var value = string.Format(format ?? name, arguments);
return new LocalizedString(name, value, resourceNotFound: format == null);
}
}
public IStringLocalizer WithCulture(CultureInfo culture)
{
CultureInfo.DefaultThreadCurrentCulture = culture;
return new EFStringLocalizer(_db);
}
public IEnumerable<LocalizedString> GetAllStrings(bool includeAncestorCultures)
{
return _db.Resources
.Include(r => r.Culture)
.Where(r => r.Culture.Name == CultureInfo.CurrentCulture.Name)
.Select(r => new LocalizedString(r.Key, r.Value, true));
}
private string GetString(string name)
{
return _db.Resources
.Include(r => r.Culture)
.Where(r => r.Culture.Name == CultureInfo.CurrentCulture.Name)
.FirstOrDefault(r => r.Key == name)?.Value;
}
}
public class EFStringLocalizer<T> : IStringLocalizer<T>
{
private readonly LocalizationDbContext _db;
public EFStringLocalizer(LocalizationDbContext db)
{
_db = db;
}
public LocalizedString this[string name]
{
get
{
var value = GetString(name);
return new LocalizedString(name, value ?? name, resourceNotFound: value == null);
}
}
public LocalizedString this[string name, params object[] arguments]
{
get
{
var format = GetString(name);
var value = string.Format(format ?? name, arguments);
return new LocalizedString(name, value, resourceNotFound: format == null);
}
}
public IStringLocalizer WithCulture(CultureInfo culture)
{
CultureInfo.DefaultThreadCurrentCulture = culture;
return new EFStringLocalizer(_db);
}
public IEnumerable<LocalizedString> GetAllStrings(bool includeAncestorCultures)
{
return _db.Resources
.Include(r => r.Culture)
.Where(r => r.Culture.Name == CultureInfo.CurrentCulture.Name)
.Select(r => new LocalizedString(r.Key, r.Value, true));
}
private string GetString(string name)
{
return _db.Resources
.Include(r => r.Culture)
.Where(r => r.Culture.Name == CultureInfo.CurrentCulture.Name)
.FirstOrDefault(r => r.Key == name)?.Value;
}
}
}
In der obigen Datei implementieren wir die IStringLocalizerFactory
Schnittstelle von Entity Framework Core, um einen benutzerdefinierten Lokalisierungsdienst zu erstellen. Der wichtige Teil ist der Konstruktor von EFStringLocalizerFactory
in dem wir eine Liste aller verfügbaren Sprachen EFStringLocalizerFactory
und sie dem Datenbankkontext hinzufügen. Jede dieser Sprachdateien fungiert als separate Datenbank.
Fügen Sie nun die folgenden Dateien zum Ordner Models/Localization
hinzu:
Culture.cs
namespace DigitalShop.Models.Localization
{
public class Culture
{
public int Id { get; set; }
public string Name { get; set; }
public virtual List<Resource> Resources { get; set; }
}
}
Resource.cs
namespace DigitalShop.Models.Localization
{
public class Resource
{
public int Id { get; set; }
public string Key { get; set; }
public string Value { get; set; }
public virtual Culture Culture { get; set; }
}
}
LocalizationDbContext.cs
namespace DigitalShop.Models.Localization
{
public class LocalizationDbContext : DbContext
{
public DbSet<Culture> Cultures { get; set; }
public DbSet<Resource> Resources { get; set; }
protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
{
optionsBuilder.UseInMemoryDatabase();
}
}
}
Bei den oben genannten Dateien handelt es sich lediglich um Modelle, die mit Sprachressourcen und Kulturen DBContext
werden. Außerdem gibt es einen typischen DBContext
der von EF Core verwendet wird.
Das Letzte, was wir für all diese Arbeit brauchen, ist das Erstellen der Sprachressourcendateien. Die JSON-Dateien, in denen ein Schlüsselwertpaar für verschiedene in Ihrer App verfügbare Sprachen gespeichert wird.
In diesem Beispiel stehen unserer App nur zwei Sprachen zur Verfügung. Englisch und Persisch. Für jede der Sprachen benötigen wir zwei Dateien. Eine JSON-Datei, die Schlüssel-Wert-Paare enthält, und eine .cs
Datei, die eine Klasse mit demselben Namen wie die JSON-Datei enthält. Diese Klasse hat eine Methode, GetList
, die die JSON-Datei deserialisiert und zurückgibt. Diese Methode wird im Konstruktor von EFStringLocalizerFactory
, den wir zuvor erstellt haben.
Erstellen Sie also diese vier Dateien in Ihrem Languages
:
de-US.cs
namespace DigitalShop.Languages
{
public static class en_US
{
public static List<Resource> GetList()
{
var jsonSerializerSettings = new JsonSerializerSettings();
jsonSerializerSettings.MissingMemberHandling = MissingMemberHandling.Ignore;
return JsonConvert.DeserializeObject<List<Resource>>(File.ReadAllText("Languages/en-US.json"), jsonSerializerSettings);
}
}
}
en-US.json
[
{
"Key": "Welcome",
"Value": "Welcome"
},
{
"Key": "Hello",
"Value": "Hello"
},
]
fa-IR.cs
public static class fa_IR
{
public static List<Resource> GetList()
{
var jsonSerializerSettings = new JsonSerializerSettings();
jsonSerializerSettings.MissingMemberHandling = MissingMemberHandling.Ignore;
return JsonConvert.DeserializeObject<List<Resource>>(File.ReadAllText("Languages/fa-IR.json", Encoding.UTF8), jsonSerializerSettings);
}
}
fa-IR.json
[
{
"Key": "Welcome",
"Value": "خوش آمدید"
},
{
"Key": "Hello",
"Value": "سلام"
},
]
Wir sind alle fertig. Um nun auf die Sprachzeichenfolgen (Schlüssel-Wert-Paare) an beliebiger Stelle in Ihrem Code ( .cs
oder .cshtml
) .cs
, können Sie Folgendes tun:
in einer .cs
Datei (Controller sein oder nicht, spielt keine Rolle):
// Returns "Welcome" for en-US and "خوش آمدید" for fa-IR
var welcome = Startup._e["Welcome"];
in einer Razor-Ansichtsdatei ( .cshtml
):
<h1>@Startup._e["Welcome"]</h1>
Nur wenige Dinge zu beachten:
- Wenn Sie versuchen , einen Zugriff auf
Key
, der in der JSON - Datei oder geladen nicht existiert, erhalten Sie nur den Schlüssel wörtliche (in dem obigen Beispiel versuchen zuzugreifenStartup._e["How are you"]
wird zurückkehrenHow are you
esHow are you
unabhängig von den Spracheinstellungen, da es nicht existiert - Wenn Sie einen Zeichenfolgewert in einer
.json
, müssen Sie die App erneut starten . Andernfalls wird nur der Standardwert (Schlüsselname) angezeigt. Dies ist besonders wichtig, wenn Sie Ihre App ohne Debugging ausführen. - Mit der
appsettings.json
können Sie alle Arten von Einstellungen speichern, die Ihre App möglicherweise benötigt - Ein Neustart der App ist nicht erforderlich, wenn Sie nur die Sprach- /
appsettings.json
Dateiappsettings.json
ändernappsettings.json
. Dies bedeutet, dass Sie in Ihrer Apps-Oberfläche eine Option haben können, mit der Benutzer die Sprache / Kultur zur Laufzeit ändern können.
Hier ist die endgültige Projektstruktur:
Legen Sie die Request-Kultur über den URL-Pfad fest
Standardmäßig unterstützt die integrierte Middleware für Anforderungslokalisierung die Einstellungskultur nur über Abfrage, Cookie oder den Accept-Language
Header. Dieses Beispiel zeigt, wie eine Middleware erstellt wird, mit der die Kultur als Teil des Pfads wie in /api/en-US/products
.
Bei dieser Beispiel-Middleware wird davon ausgegangen, dass sich das Gebietsschema im zweiten Segment des Pfads befindet.
public class UrlRequestCultureProvider : RequestCultureProvider
{
private static readonly Regex LocalePattern = new Regex(@"^[a-z]{2}(-[a-z]{2,4})?$",
RegexOptions.IgnoreCase);
public override Task<ProviderCultureResult> DetermineProviderCultureResult(HttpContext httpContext)
{
if (httpContext == null)
{
throw new ArgumentNullException(nameof(httpContext));
}
var url = httpContext.Request.Path;
// Right now it's not possible to use httpContext.GetRouteData()
// since it uses IRoutingFeature placed in httpContext.Features when
// Routing Middleware registers. It's not set when the Localization Middleware
// is called, so this example simply assumes the locale will always
// be located in the second segment of a path, like in /api/en-US/products
var parts = httpContext.Request.Path.Value.Split('/');
if (parts.Length < 3)
{
return Task.FromResult<ProviderCultureResult>(null);
}
if (!LocalePattern.IsMatch(parts[2]))
{
return Task.FromResult<ProviderCultureResult>(null);
}
var culture = parts[2];
return Task.FromResult(new ProviderCultureResult(culture));
}
}
Middleware-Registrierung
var localizationOptions = new RequestLocalizationOptions
{
SupportedCultures = new List<CultureInfo>
{
new CultureInfo("de-DE"),
new CultureInfo("en-US"),
new CultureInfo("en-GB")
},
SupportedUICultures = new List<CultureInfo>
{
new CultureInfo("de-DE"),
new CultureInfo("en-US"),
new CultureInfo("en-GB")
},
DefaultRequestCulture = new RequestCulture("en-US")
};
// Adding our UrlRequestCultureProvider as first object in the list
localizationOptions.RequestCultureProviders.Insert(0, new UrlRequestCultureProvider
{
Options = localizationOptions
});
app.UseRequestLocalization(localizationOptions);
Benutzerdefinierte Routenbeschränkungen
Hinzufügen und Erstellen von benutzerdefinierter Route Einschränkungen in der gezeigten Strecke beschränkt Beispiel. Die Verwendung von Einschränkungen vereinfacht die Verwendung von benutzerdefinierten Routenbeschränkungen.
Die Route registrieren
Beispiel für das Registrieren der Routen ohne benutzerdefinierte Einschränkungen
app.UseMvc(routes =>
{
routes.MapRoute(
name: "default",
template: "api/{culture::regex(^[a-z]{{2}}-[A-Za-z]{{4}}$)}}/{controller}/{id?}");
routes.MapRoute(
name: "default",
template: "api/{controller}/{id?}");
});