Sök…


Lokalisering med hjälp av JSON-språkresurser

I ASP.NET Core finns det flera olika sätt vi kan lokalisera / globalisera vår app. Det är viktigt att välja ett sätt som passar dina behov. I det här exemplet ser du hur vi kan skapa en flerspråkig ASP.NET Core-app som läser språkspecifika strängar från .json filer och lagrar dem i minnet för att ge lokalisering i alla delar av appen samt upprätthålla en hög prestanda.

Så vi gör det är att använda Microsoft.EntityFrameworkCore.InMemory paketet.

Anmärkningar:

  1. Namnområdet för detta projekt är DigitalShop som du kan ändra till ditt projekt eget namnområde
  2. Överväg att skapa ett nytt projekt så att du inte stöter på konstiga fel
  3. Det här exemplet visar inte på bästa sätt, så om du tror att det kan förbättras vänligen redigera det

För att börja låt oss lägga till följande paket till det befintliga dependencies i project.json filen:

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

Låt oss nu ersätta filen Startup.cs med: ( using uttalanden tas bort eftersom de enkelt kan läggas till senare)

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

I ovanstående kod lägger vi först till tre public static fältvariabler som vi senare kommer att initiera med de värden som läses från inställningsfilen.

I klassen konstruktör för Startup lägger vi till en json-inställningsfil till builder . Den första filen krävs för att appen ska fungera, så fortsätt och skapa appsettings.json i ditt projektrot om den inte redan finns. Med Visual Studio 2015 skapas den här filen automatiskt, så bara ändra innehållet till: (Du kan utelämna avsnittet Logging om du inte använder den)

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

Gå framåt och skapa tre mappar i projektroten:

Models , Services och Languages . Skapa en annan mapp med namnet Localization mappen Models .

I mappen Services skapar vi en ny .cs-fil med namnet EFLocalization . Innehållet skulle vara: (Återigen using uttalanden)

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

I ovanstående fil implementerar IStringLocalizerFactory gränssnittet IStringLocalizerFactory från Entity Framework Core för att skapa en anpassad lokaliseringstjänst. Den viktiga delen är konstruktören av EFStringLocalizerFactory där vi gör en lista över alla tillgängliga språk och lägger till den i databasesammanhang. Var och en av dessa språkfiler fungerar som en separat databas.

Lägg nu till alla följande filer i mappen 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();
        }
    }
}

Ovanstående filer är bara modeller som kommer att fyllas med språkresurser, kulturer och det finns också en typisk DBContext används av EF Core.

Det sista vi behöver för att göra allt detta arbete är att skapa språkresursfilerna. JSON-filerna som används för att lagra ett nyckelvärdespar för olika språk tillgängliga i din app.

I det här exemplet har vår app bara två språk tillgängliga. Engelska och persiska. För varje språk behöver vi två filer. En JSON-fil som innehåller nyckelvärdespar och en .cs fil som innehåller en klass med samma namn som JSON-fil. Den klassen har en metod, GetList som deserialiserar JSON-filen och returnerar den. Den här metoden kallas i konstruktören för EFStringLocalizerFactory som vi skapade tidigare.

Så skapar du dessa fyra filer i mappen 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": "سلام"
  },
]

Vi är alla klara. För att få tillgång till språksträngarna (nyckelvärdespar) var som helst i din kod ( .cs eller .cshtml ) kan du göra följande:

i en .cs fil (vara Controller eller inte, spelar ingen roll):

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

i en Razor-visningsfil ( .cshtml ):

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

Få saker att tänka på:

  • Om du försöker komma åt en Key som inte finns i JSON-fil eller laddas, kommer du bara få nyckeln bokstav (i exemplet ovan, försöker komma åt Startup._e["How are you"] kommer tillbaka How are you oavsett språkinställningar eftersom det inte finns
  • Om du ändrar ett strängvärde i en språk .json fil måste du starta om appen. Annars visar det bara standardvärdet (nyckelnamn). Detta är särskilt viktigt när du kör din app utan felsökning.
  • appsettings.json kan användas för att lagra alla typer av inställningar som din app kan behöva
  • Omstart av appen är inte nödvändig om du bara vill ändra språk / kulturinställningar från appsettings.json filen . Detta innebär att du kan ha ett alternativ i ditt appgränssnitt för att låta användare ändra språk / kultur under körning.

Här är den slutliga projektstrukturen:

ange bildbeskrivning här

Ställ in begärarkultur via url-sökväg

Som standard stöder den inbyggda begäran mellanprogramvaran endast inställning av kultur via fråga, cookie eller Accept-Language rubriken. Det här exemplet visar hur du skapar ett mellanprogram som gör det möjligt att ställa in kulturen som en del av sökvägen som i /api/en-US/products .

Det här exemplet mellanprogrammet antar att landskriften är i det andra segmentet av sökvägen.

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

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

Anpassade ruttbegränsningar

Lägga till och skapa anpassade ruttbegränsningar visas i exemplet Ruttbegränsningar . Att använda begränsningar förenklar användningen av anpassade ruttbegränsningar.

Registrerar rutten

Exempel på registrering av rutter utan att använda anpassade begränsningar

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
Licensierat under CC BY-SA 3.0
Inte anslutet till Stack Overflow