asp.net-core
Localisation
Recherche…
Localisation à l'aide de ressources de langage JSON
Dans ASP.NET Core, il existe différentes manières de localiser / globaliser notre application. Il est important de choisir un moyen adapté à vos besoins. Dans cet exemple, vous verrez comment créer une application ASP.NET Core multilingue qui lit des chaînes spécifiques à une langue à partir de fichiers .json
et les stocke en mémoire pour assurer la localisation dans toutes les sections de l’application et maintenir des performances élevées.
Nous procédons ainsi en utilisant Microsoft.EntityFrameworkCore.InMemory
package Microsoft.EntityFrameworkCore.InMemory
.
Remarques:
- L’espace de noms de ce projet est
DigitalShop
que vous pouvez modifier dans votre propre espace de noms de projets. - Envisagez de créer un nouveau projet afin de ne pas rencontrer d'erreurs étranges
- En aucun cas cet exemple ne montre les meilleures pratiques, donc si vous pensez que cela peut être amélioré, veuillez le modifier
Pour commencer, ajoutons les packages suivants à la section dependencies
existante du fichier project.json
:
"Microsoft.EntityFrameworkCore": "1.0.0",
"Microsoft.EntityFrameworkCore.SqlServer": "1.0.0",
"Microsoft.EntityFrameworkCore.InMemory": "1.0.0"
Maintenant , nous allons remplacer le Startup.cs
fichier: ( en using
les états sont supprimés car ils peuvent être facilement ajoutés plus tard)
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));
}
}
}
Dans le code ci-dessus, nous ajoutons d'abord trois variables de champs public static
que nous initialiserons ultérieurement à l'aide des valeurs lues dans le fichier de paramètres.
Dans le constructeur de la classe Startup
, nous ajoutons un fichier de paramètres json à la variable de builder
. Le premier fichier est requis pour que l'application fonctionne, alors n'hésitez pas à créer appsettings.json
dans la racine de votre projet si elle n'existe pas déjà. À l'aide de Visual Studio 2015, ce fichier est créé automatiquement. Changez simplement son contenu pour: (vous pouvez omettre la section de Logging
si vous ne l'utilisez pas)
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
}
}
À l'avenir, créez trois dossiers dans la racine de votre projet:
Models
, Services
et Languages
. Dans le dossier Models
, créez un autre dossier nommé Localization
.
Dans le dossier Services
, nous créons un nouveau fichier .cs nommé EFLocalization
. Le contenu serait: (Encore une fois à l' using
ne sont pas inclus déclarations)
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;
}
}
}
Dans le fichier ci-dessus, nous implémentons l'interface IStringLocalizerFactory
depuis Entity Framework Core afin de créer un service de localisation personnalisé. La partie importante est le constructeur de EFStringLocalizerFactory
où nous faisons une liste de toutes les langues disponibles et l’ajoutons au contexte de la base de données. Chacun de ces fichiers de langue agit comme une base de données distincte.
Ajoutez maintenant chacun des fichiers suivants au dossier Models/Localization
:
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();
}
}
}
Les fichiers ci-dessus ne sont que des modèles qui seront remplis avec des ressources linguistiques, des cultures et un DBContext
typique utilisé par EF Core.
La dernière chose dont nous avons besoin pour faire tout ce travail est de créer les fichiers de ressources linguistiques. Les fichiers JSON utilisés pour stocker une paire clé-valeur pour différentes langues disponibles dans votre application.
Dans cet exemple, notre application ne dispose que de deux langues. Anglais et persan. Pour chacune des langues, nous avons besoin de deux fichiers. Un fichier JSON contenant des paires clé-valeur et un fichier .cs
contenant une classe portant le même nom que le fichier JSON. Cette classe a une méthode, GetList
qui désérialise le fichier JSON et le renvoie. Cette méthode est appelée dans le constructeur de EFStringLocalizerFactory
que nous avons créé précédemment.
Donc, créez ces quatre fichiers dans votre dossier Languages
:
en-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": "سلام"
},
]
Nous avons tous fini. Maintenant, pour accéder aux chaînes de langue (paires clé-valeur) n'importe où dans votre code ( .cs
ou .cshtml
), vous pouvez effectuer les opérations suivantes:
dans un fichier .cs
(soit Controller ou non, peu importe):
// Returns "Welcome" for en-US and "خوش آمدید" for fa-IR
var welcome = Startup._e["Welcome"];
dans un fichier de vue Razor ( .cshtml
):
<h1>@Startup._e["Welcome"]</h1>
Peu de choses à retenir:
- Si vous essayez d'accéder à une
Key
qui n'existe pas dans le fichier JSON ou chargé, vous obtiendrez juste le littéral clé (dans l'exemple ci - dessus, en essayant d'accéderStartup._e["How are you"]
-How are you
Startup._e["How are you"]
retournerezHow are you
-How are you
peu importe les paramètres linguistiques, car il n'existe pas - Si vous modifiez une valeur de chaîne dans un fichier de langue
.json
, vous devrez redémarrer l'application. Sinon, il affichera simplement la valeur par défaut (nom de la clé). Ceci est particulièrement important lorsque vous exécutez votre application sans déboguer. - Le
appsettings.json
peut être utilisé pour stocker toutes sortes de paramètres dont votre application peut avoir besoin - Le redémarrage de l'application n'est pas nécessaire si vous souhaitez simplement modifier les paramètres de langue / culture du fichier
appsettings.json
. Cela signifie que vous pouvez avoir une option dans l'interface de vos applications pour permettre aux utilisateurs de modifier la langue / culture au moment de l'exécution.
Voici la structure finale du projet:
Définir la culture de la demande via le chemin de l'URL
Par défaut, le middleware Request Localization intégré prend uniquement en charge la définition de la culture via une requête, un cookie ou un en Accept-Language
tête Accept-Language
. Cet exemple montre comment créer un middleware qui permet de définir la culture comme faisant partie du chemin comme dans /api/en-US/products
.
Cet exemple de middleware suppose que les paramètres régionaux se trouvent dans le deuxième segment du chemin.
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));
}
}
Enregistrement du middleware
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);
Contraintes d'itinéraire personnalisé
L'ajout et la création de contraintes de route personnalisées sont illustrés dans l'exemple des contraintes de route . L'utilisation de contraintes simplifie l'utilisation des contraintes de route personnalisées.
Enregistrer l'itinéraire
Exemple d'enregistrement des routes sans utiliser de contraintes personnalisées
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?}");
});