Zoeken…


Lokalisatie met behulp van JSON-taalbronnen

In ASP.NET Core zijn er verschillende manieren waarop we onze app kunnen lokaliseren / globaliseren. Het is belangrijk om een manier te kiezen die bij u past. In dit voorbeeld ziet u hoe we een meertalige ASP.NET Core-app kunnen maken die taalspecifieke tekenreeksen uit .json bestanden leest en deze in het geheugen opslaat om lokalisatie in alle delen van de app te bieden en hoge prestaties te behouden.

De manier waarop we het doen is met behulp van Microsoft.EntityFrameworkCore.InMemory pakket.

Opmerkingen:

  1. De naamruimte voor dit project is DigitalShop die u kunt wijzigen in de eigen naamruimte van uw project
  2. Overweeg een nieuw project te maken, zodat u geen rare fouten tegenkomt
  3. Dit voorbeeld laat in geen geval de praktische tips zien, dus als u denkt dat het kan worden verbeterd, kunt u het vriendelijk bewerken

Laten we om te beginnen de volgende pakketten toevoegen aan de bestaande dependencies in het bestand project.json :

"Microsoft.EntityFrameworkCore": "1.0.0",
"Microsoft.EntityFrameworkCore.SqlServer": "1.0.0",
"Microsoft.EntityFrameworkCore.InMemory": "1.0.0"

Laten we nu het Startup.cs bestand vervangen door: ( using instructies worden verwijderd omdat ze later eenvoudig kunnen worden toegevoegd)

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));
        }
    }
}

In de bovenstaande code voegen we eerst drie public static veldvariabelen toe die we later zullen initialiseren met behulp van de waarden die worden gelezen uit het instellingenbestand.

In de constructor voor Startup klasse een JSON-instellingen bestand toe te voegen we aan de builder variabele. Het eerste bestand is vereist om de app te laten werken, dus ga je gang en maak appsettings.json in je project root als deze nog niet bestaat. Met Visual Studio 2015 wordt dit bestand automatisch gemaakt, dus wijzig de inhoud gewoon in: (U kunt het gedeelte Logging weglaten als u het niet gebruikt)

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
  }
}

Maak voortaan drie mappen in uw projectwortel:

Models , Services en Languages . Maak in de map Models een andere map met de naam Localization .

In de map Services maken we een nieuw .cs-bestand met de naam EFLocalization . De inhoud zou zijn: (Opnieuw using verklaringen zijn niet inbegrepen)

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 het bovenstaande bestand implementeren we de IStringLocalizerFactory interface van Entity Framework Core om een aangepaste lokaliseringsservice te maken. Het belangrijke onderdeel is de constructor van EFStringLocalizerFactory waar we een lijst maken van alle beschikbare talen en deze toevoegen aan de databasecontext. Elk van deze taalbestanden fungeert als een afzonderlijke database.

Voeg nu elk van de volgende bestanden toe aan de map 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();
        }
    }
}

De bovenstaande bestanden zijn slechts modellen die worden gevuld met DBContext , culturen en er is ook een typische DBContext gebruikt door EF Core.

Het laatste wat we nodig hebben om dit allemaal te laten werken, is het maken van de taalresourcebestanden. De JSON-bestanden die worden gebruikt om een sleutel / waarde-paar op te slaan voor verschillende talen die beschikbaar zijn in uw app.

In dit voorbeeld heeft onze app slechts twee talen beschikbaar. Engels en Perzisch. Voor elk van de talen hebben we twee bestanden nodig. Een JSON-bestand met sleutel / waarde-paren en een .cs bestand met een klasse met dezelfde naam als het JSON-bestand. Die klasse heeft één methode, GetList die het JSON-bestand deserialiseert en retourneert. Deze methode wordt de constructor van EFStringLocalizerFactory die we eerder hebben gemaakt.

Maak dus deze vier bestanden in uw map 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": "سلام"
  },
]

We zijn allemaal klaar. Om toegang te krijgen tot de .cshtml (sleutel / waarde-paren) overal in uw code ( .cs of .cshtml ) kunt u het volgende doen:

in een .cs bestand (wees Controller of niet, maakt niet uit):

// Returns "Welcome" for en-US and "خوش آمدید" for fa-IR
var welcome = Startup._e["Welcome"];

in een Razor-weergavebestand ( .cshtml ):

<h1>@Startup._e["Welcome"]</h1>

Enkele dingen om in gedachten te houden:

  • Als u toegang probeert te krijgen tot een Key die niet bestaat in het JSON-bestand of is geladen, krijgt u alleen de letterlijke sleutel (in het bovenstaande voorbeeld probeert u toegang te krijgen tot Startup._e["How are you"] zal terugkeren How are you ongeacht de taalinstellingen omdat deze niet bestaat
  • Als u een tekenreekswaarde in een .json bestand voor een taal .json , moet u de app OPNIEUW STARTEN . Anders wordt alleen de standaardwaarde (sleutelnaam) weergegeven. Dit is vooral belangrijk wanneer u uw app uitvoert zonder foutopsporing.
  • De appsettings.json kan worden gebruikt om allerlei instellingen op te slaan die uw app mogelijk nodig heeft
  • Het opnieuw opstarten van de app is niet nodig als u alleen de taal- / cultuurinstellingen van het bestand appsettings.json wijzigen . Dit betekent dat u een optie in uw apps-interface kunt hebben waarmee gebruikers de taal / cultuur tijdens runtime kunnen wijzigen.

Hier is de laatste projectstructuur:

voer hier de afbeeldingsbeschrijving in

Stel Verzoekcultuur in via URL-pad

Standaard ondersteunt de ingebouwde middleware voor verzoeklokalisatie alleen het instellen van een cultuur via een query, cookie of een koptekst van de Accept-Language . Dit voorbeeld laat zien hoe een middleware wordt gemaakt waarmee de cultuur kan worden ingesteld als onderdeel van het pad zoals in /api/en-US/products .

In dit voorbeeld middleware wordt ervan uitgegaan dat de landinstelling zich in het tweede segment van het pad bevindt.

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-registratie

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);

Aangepaste routebeperkingen

Het toevoegen en maken van aangepaste routebeperkingen wordt weergegeven in het voorbeeld Routebeperkingen . Het gebruik van beperkingen vereenvoudigt het gebruik van aangepaste routebeperkingen.

De route registreren

Voorbeeld van het registreren van de routes zonder aangepaste beperkingen te gebruiken

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?}"); 
});


Modified text is an extract of the original Stack Overflow Documentation
Licentie onder CC BY-SA 3.0
Niet aangesloten bij Stack Overflow