수색…


JSON 언어 리소스를 사용하여 지역화

ASP.NET 코어에는 앱을 현지화 / 세계화 할 수있는 여러 가지 방법이 있습니다. 귀하의 필요에 맞는 방법을 고르는 것이 중요합니다. 이 예제에서는 .json 파일에서 언어 별 문자열을 읽고이를 메모리에 저장하여 응용 프로그램의 모든 섹션에서 현지화를 제공하고 고성능을 유지하는 다국어 ASP.NET 핵심 응용 프로그램을 만드는 방법을 보여줍니다.

우리가하는 일은 Microsoft.EntityFrameworkCore.InMemory 패키지를 사용하는 것입니다.

노트:

  1. 이 프로젝트의 네임 스페이스는 DigitalShop 으로, 프로젝트 네임 스페이스로 변경할 수 있습니다.
  2. 이상한 오류가 발생하지 않도록 새 프로젝트를 만드는 것을 고려하십시오.
  3. 이 예제가 모범 사례를 보여주는 것은 결코 아닙니다. 따라서 개선 할 수 있다고 생각한다면 친절하게 편집하십시오.

시작하려면 project.json 파일의 기존 dependencies 섹션에 다음 패키지를 추가합니다.

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

이제 Startup.cs 파일을 다음과 같이 Startup.cs : ( using 문은 나중에 쉽게 추가 할 수 있으므로 제거됨)

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

위의 코드에서 세 개의 public static 필드 변수를 먼저 추가합니다.이 변수는 나중에 설정 파일에서 읽은 값을 사용하여 초기화합니다.

Startup 클래스의 생성자에서 json 설정 파일을 builder 변수에 추가합니다. 앱이 작동하려면 첫 번째 파일이 필요합니다. 프로젝트 루트에 appsettings.json 이 없으면 만들어야합니다. Visual Studio 2015를 사용하면이 파일이 자동으로 만들어 지므로 내용을 다음과 같이 변경하십시오. (사용하지 않으면 Logging 섹션을 생략 할 수 있습니다)

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

앞으로 프로젝트 루트에 세 개의 폴더를 만듭니다.

Models , ServicesLanguages . Models 폴더에서 Localization 라는 다른 폴더를 만듭니다.

Services 폴더에서 EFLocalization 이라는 새 .cs 파일을 EFLocalization . 내용은 다음과 같습니다 (다시 문장을 using 하는 것은 포함되지 않습니다)

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

위의 파일에서 사용자 지정 로컬 라이저 서비스를 만들기 위해 Entity Framework Core에서 IStringLocalizerFactory 인터페이스를 구현합니다. 중요한 부분은 EFStringLocalizerFactory 의 생성자입니다 EFStringLocalizerFactory 여기서 우리는 사용 가능한 모든 언어 목록을 만들어 데이터베이스 컨텍스트에 추가합니다. 이러한 언어 파일은 각각 별도의 데이터베이스로 작동합니다.

이제 다음 파일 각각을 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();
        }
    }
}

위의 파일은 언어 자원, 문화로 채워질 모델 DBContext 아니라 EF 코어에서 사용되는 일반적인 DBContext 도 있습니다.

이 모든 작업을 수행하는 데 필요한 마지막 사항은 언어 리소스 파일을 만드는 것입니다. 앱에서 사용할 수있는 다양한 언어의 키 - 값 쌍을 저장하는 데 사용되는 JSON 파일입니다.

이 예제에서 우리의 앱은 두 가지 언어 만 사용할 수 있습니다. 영어와 페르시아어. 각 언어에 대해 두 개의 파일이 필요합니다. 키 - 값 쌍을 포함하는 JSON 파일과 JSON 파일과 이름이 같은 클래스가 포함 된 .cs 파일. 이 클래스에는 JSON 파일을 deserialize하고 반환하는 GetList 라는 하나의 메서드가 있습니다. 이 메소드는 이전에 작성한 EFStringLocalizerFactory 의 생성자에서 호출됩니다.

따라서 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": "سلام"
  },
]

우리 모두 다 끝났어. 이제 코드 ( .cs 또는 .cshtml )에서 언어 문자열 (키 - 값 쌍)에 액세스하려면 다음을 수행 할 수 있습니다.

.cs 파일에서 (컨트롤러가 되든 상관 .cs 상관 없습니다) :

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

면도기 뷰 파일 ( .cshtml )에서

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

명심할 것 :

  • 당신이 액세스하려고하면 Key JSON 파일에 존재 또는로드되지 않습니다, 당신은 단지에 액세스하려고, 위의 예에서 키 문자를 (얻을 것이다 Startup._e["How are you"] 반환합니다 How are you 존재하지 않기 때문에 언어 설정에 관계없이
  • 당신이 언어의 문자열 값을 변경하는 경우 .json 파일, 앱을 다시 시작해야합니다. 그렇지 않으면 기본값 (키 이름) 만 표시됩니다. 이것은 디버깅하지 않고 앱을 실행할 때 특히 중요합니다.
  • appsettings.json 은 앱에서 필요할 수있는 모든 종류의 설정을 저장하는 데 사용할 수 있습니다.
  • appsettings.json 파일에서 언어 / 문화 설정 을 변경하려는 경우 앱을 다시 시작 하지 않아도 됩니다. 즉, 앱 인터페이스에 사용자가 런타임에 언어 / 문화를 변경할 수있는 옵션을 가질 수 있습니다.

다음은 최종 프로젝트 구조입니다.

여기에 이미지 설명을 입력하십시오.

URL 경로를 통해 문화권 요청하기

기본적으로 기본 제공 Request Localization 미들웨어는 query, cookie 또는 Accept-Language 헤더를 통한 culture 설정 만 지원합니다. 이 예제는 경로를 /api/en-US/products 와 같이 경로의 일부로 설정할 수있는 미들웨어를 만드는 방법을 보여줍니다.

이 예제 미들웨어는 로켈이 경로의 두 번째 세그먼트에 있다고 가정합니다.

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

미들웨어 등록

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

맞춤 경로 제한

사용자 지정 라우트 구속 조건 추가 및 작성은 라우트 구속 조건 예제에 표시됩니다. 제약 조건을 사용하면 맞춤 라우트 제약 조건의 사용이 단순 해집니다.

경로 등록

사용자 지정 제약 조건을 사용하지 않고 경로를 등록하는 예

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
아래 라이선스 CC BY-SA 3.0
와 제휴하지 않음 Stack Overflow