使用 JSON 語言資源進行本地化
在 ASP.NET Core 中,有幾種不同的方法可以本地化/全球化我們的應用程式。選擇適合你需求的方式非常重要。在這個例子中,你將看到我們如何製作一個多語言 ASP.NET Core 應用程式,該應用程式從 .json
檔案中讀取特定於語言的字串並將其儲存在記憶體中,以便在應用程式的所有部分提供本地化以及保持高效能。
我們這樣做的方法是使用 Microsoft.EntityFrameworkCore.InMemory
包。
筆記:
- 此專案的名稱空間是
DigitalShop
,你可以將其更改為專案自己的名稱空間 - 考慮建立一個新專案,這樣就不會遇到奇怪的錯誤
- 絕不是這個例子展示了最佳實踐,所以如果你認為它可以改進,請善於編輯
首先,讓我們將以下包新增到 project.json
檔案中的現有 dependencies
部分:
"Microsoft.EntityFrameworkCore": "1.0.0",
"Microsoft.EntityFrameworkCore.SqlServer": "1.0.0",
"Microsoft.EntityFrameworkCore.InMemory": "1.0.0"
現在讓我們用以下程式碼替換 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
,Services
和 Languages
。在 Models
資料夾中建立另一個名為 Localization
的資料夾。
在 Services
資料夾中,我們建立了一個名為 EFLocalization
的新 .cs 檔案。內容如下:(不包括 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
的建構函式,我們在其中列出所有可用語言並將其新增到資料庫上下文中。這些語言檔案中的每一個都充當單獨的資料庫。
現在將以下每個檔案新增到 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();
}
}
}
上述檔案只是將填充語言資源,文化的模型,還有 EF Core 使用的典型 DBContext
。
我們需要做的最後一件事就是建立語言資原始檔。JSON 檔案用於儲存應用中可用的不同語言的鍵值對。
在此示例中,我們的應用程式僅提供兩種語言。英語和波斯語。對於每種語言,我們需要兩個檔案。包含鍵值對的 jSON 檔案和包含與 JSON 檔案同名的類的 .cs
檔案。該類有一個方法,GetList
反序列化 JSON 檔案並返回它。在我們之前建立的 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
檔案中(無論是否為控制器,無所謂):
// Returns "Welcome" for en-US and "خوش آمدید" for fa-IR
var welcome = Startup._e["Welcome"];
在剃刀檢視檔案(.cshtml
)中:
<h1>@Startup._e["Welcome"]</h1>
要記住的幾件事情:
- 如果你嘗試訪問 JSON 檔案中不存在或載入的
Key
,你將獲得金鑰文字(在上面的示例中,嘗試訪問Startup._e["How are you"]
將返回How are you
,無論語言設定是否因為它不存在 - 如果更改語言
.json
檔案中的字串值,則需要重新啟動應用程式。否則它只顯示預設值(鍵名)。當你在沒有除錯的情況下執行應用程式時,這一點尤為重要。 appsettings.json
可用於儲存你的應用可能需要的各種設定- 重新啟動應用程式是沒有必要的,如果你只是想改變從
appsettings.json
檔案語言/文化設定。這意味著你可以在應用程式介面中選擇一個選項,以便使用者在執行時更改語言/文化。
這是最終的專案結構: