Ricerca…


Localizzazione tramite risorse di linguaggio JSON

In ASP.NET Core ci sono diversi modi in cui possiamo localizzare / globalizzare la nostra app. È importante scegliere un modo adatto alle tue esigenze. In questo esempio vedremo come possiamo creare un'app di ASP.NET multilingue che legge stringhe specifiche della lingua da file .json e li memorizza in memoria per fornire la localizzazione in tutte le sezioni dell'app e mantenere alte prestazioni.

Il modo in cui lo facciamo è utilizzando il pacchetto Microsoft.EntityFrameworkCore.InMemory .

Gli appunti:

  1. Lo spazio dei nomi per questo progetto è DigitalShop che puoi modificare lo spazio dei nomi del tuo progetto
  2. Prendi in considerazione la possibilità di creare un nuovo progetto in modo da non incorrere in strani errori
  3. In nessun modo questo esempio mostra le migliori pratiche, quindi se pensi che possa essere migliorato, ti preghiamo gentilmente di modificarlo

Per iniziare, aggiungiamo i seguenti pacchetti alla sezione delle dependencies esistenti nel file project.json :

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

Ora cerchiamo di sostituire lo Startup.cs file con: ( using le dichiarazioni vengono rimossi in quanto possono essere facilmente aggiunti in seguito)

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

Nel codice precedente, per prima cosa aggiungiamo tre variabili di campo public static che verranno successivamente inizializzate utilizzando i valori letti dal file delle impostazioni.

Nel costruttore per la classe Startup aggiungiamo un file di impostazioni json alla variabile builder . Il primo file è necessario affinché l'app funzioni, quindi vai avanti e crea appsettings.json nella root del progetto, se non esiste già. Utilizzando Visual Studio 2015, questo file viene creato automaticamente, quindi basta cambiarne il contenuto in: (Puoi omettere la sezione Logging se non la usi)

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

Andando avanti, crea tre cartelle nella root del tuo progetto:

Models , Services e Languages . Nella cartella Models crea un'altra cartella denominata Localization .

Nella cartella Services creiamo un nuovo file .cs denominato EFLocalization . Il contenuto sarebbe: (Anche in questo caso l' using dichiarazioni non è incluso)

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

Nel file sopra implementiamo l'interfaccia IStringLocalizerFactory da Entity Framework Core per creare un servizio di localizzazione personalizzato. La parte importante è il costruttore di EFStringLocalizerFactory cui creiamo un elenco di tutte le lingue disponibili e lo aggiungiamo al contesto del database. Ciascuno di questi file di lingua agisce come un database separato.

Ora aggiungi ognuno dei seguenti file alla cartella 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();
        }
    }
}

I file di cui sopra sono solo modelli che verranno popolati con risorse linguistiche, culture e c'è anche un tipico DBContext utilizzato da EF Core.

L'ultima cosa di cui abbiamo bisogno per fare tutto questo è creare i file delle risorse linguistiche. I file JSON utilizzati per memorizzare una coppia chiave-valore per lingue diverse disponibili nella tua app.

In questo esempio la nostra app ha solo due lingue disponibili. Inglese e persiano. Per ognuna delle lingue abbiamo bisogno di due file. Un file JSON contenente coppie chiave-valore e un file .cs che contiene una classe con lo stesso nome del file JSON. Quella classe ha un metodo, GetList che deserializza il file JSON e lo restituisce. Questo metodo è chiamato nel costruttore di EFStringLocalizerFactory che abbiamo creato in precedenza.

Quindi, crea questi quattro file nella cartella 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": "سلام"
  },
]

Abbiamo finito. Ora per accedere alle stringhe della lingua (coppie chiave-valore) in qualsiasi punto del codice ( .cs o .cshtml ) puoi fare quanto segue:

in un file .cs (essere Controller o meno, non importa):

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

in un file di visualizzazione Razor ( .cshtml ):

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

Poche cose da tenere a mente:

  • Se si tenta di accedere a una Key che non esiste nel file JSON o caricata, si otterrà la chiave letterale (nell'esempio sopra, tentando di accedere a Startup._e["How are you"] restituirà How are you non importa le impostazioni della lingua perché non esiste
  • Se si modifica un valore stringa in una lingua .json file, sarà necessario riavviare l'applicazione. Altrimenti mostrerà solo il valore predefinito (nome della chiave). Questo è particolarmente importante quando esegui la tua app senza eseguire il debug.
  • Il appsettings.json può essere utilizzato per memorizzare tutti i tipi di impostazioni di cui l'app potrebbe aver bisogno
  • Il riavvio dell'app non è necessario se si desidera semplicemente modificare le impostazioni di lingua / cultura dal file appsettings.json . Ciò significa che è possibile avere un'opzione nell'interfaccia delle app per consentire agli utenti di cambiare lingua / cultura in fase di runtime.

Ecco la struttura finale del progetto:

inserisci la descrizione dell'immagine qui

Imposta la cultura della richiesta tramite il percorso dell'URL

Per impostazione predefinita, il middleware di richiesta localizzazione integrato supporta solo l'impostazione della cultura tramite query, cookie o intestazione Accept-Language . Questo esempio mostra come creare un middleware che consente di impostare la cultura come parte del percorso come in /api/en-US/products .

Questo middleware di esempio presuppone che le impostazioni internazionali si trovino nel secondo segmento del percorso.

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

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

Vincoli di percorso personalizzati

L'aggiunta e la creazione di vincoli di instradamento personalizzati sono mostrati nell'esempio Vincoli percorso . L'uso dei vincoli semplifica l'utilizzo dei vincoli del percorso personalizzato.

Registrazione del percorso

Esempio di registrazione dei percorsi senza l'utilizzo di vincoli personalizzati

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
Autorizzato sotto CC BY-SA 3.0
Non affiliato con Stack Overflow