Globalization and localization in ASP.NET Core Part 1
前言
MSDN官網文章:ASP.NET Core 全球化和當地語系化
稍微看了官網文章覺得真是文言文Orz,自己整理一下,簡單提供Sample Code和說明,有需要的網友就自己Copy Paste
這種多國語系功能只適合網頁上不變動文字,如果網頁內容有ckeditor套件或留言版等等會讓使用者自行上稿文章的話,想實作多國語系功能就考慮使用Google相關API
實作
※本文版本為ASP.net Core 2.1
1.CultureInfo.CurrentCulture和日期、貨幣格式有關;CultureInfo.CurrentUICulture和語言有關。
2.每個Request預設根據RequestLocalizationOptions物件的Provider決定文化特性
預設Provider包含:
QueryStringRequestCultureProvider:在網址列加上查詢字串?culture=en-US或?culture=zh-TW,如下(個人覺得偏向開發偵錯使用)

CookieRequestCultureProvider: 讀取用戶端的Cookie來判斷是否有支持的文化特性,如果要讓用戶自己手動切換網頁語系,會需要用到這個
※2018.09.05追加 CookieRequestCultureProvider 的範例程式碼,請見文章下方

AcceptLanguageHeaderRequestCultureProvider: 讀取用戶端瀏覽器Header中的Accept-Language來判斷是否有支持的文化特性,通常用在自動切換網頁語系,本文範例程式碼以此為主。
上述預設Provider採用優先順序為QueryStringRequestCultureProvider > CookieRequestCultureProvider > AcceptLanguageHeaderRequestCultureProvider
說白話點,雖然瀏覽器header中的Accept-Language有zh-TW,但如果用戶在網址列上加入查詢字串?culture=en-US而且剛好en-US程式有支持的話,那麼會優先使用QueryStringRequestCultureProvider (文章最後有執行圖片可參考)
反之,如果用戶在網址列上加入查詢字串?culture=ja-JP,剛好程式不支持ja-JP的話,那程式就有可能採用CookieRequestCultureProvider或AcceptLanguageHeaderRequestCultureProvider
3.如果從QueryString、Cookie、瀏覽器的Accept-Language,程式都無法判斷該使用哪個Provider的話,則文化特性會採用DefaultRequestCulture的設定當做預設值(待會看程式碼就懂了)
4.在Controller使用 IStringLocalizer<T>實現多國語系功能
5.在View使用 IViewLocalizer實現多國語系功能
6.最後還有資源檔*.resx,存放路徑和命名有一定規則
7.Data Annotation當地語系化.....讓我拖搞一下,有空研究Orz
以下是Startup.cs程式碼
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Hosting;
using Microsoft.AspNetCore.Http;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
/*引用命名空間*/
using System.Globalization;
using Microsoft.AspNetCore.Mvc.Razor;
using Microsoft.AspNetCore.Localization;
namespace StarterM
{
    public class Startup
    {
          
        public void ConfigureServices(IServiceCollection services)
        { 
            //資源檔所在資料夾
            services.AddLocalization(options=>options.ResourcesPath="Resources");
            services.AddMvc()
                    .AddViewLocalization(LanguageViewLocationExpanderFormat.Suffix);//要使用View多國語系的話就加這行程式碼
        }
         
        public void Configure(IApplicationBuilder app, IHostingEnvironment env)
        { 
            if (env.IsDevelopment())
            {
                app.UseDeveloperExceptionPage();
            }
            var supportedCultures = new CultureInfo[] {
                new CultureInfo("en-US"),
                new CultureInfo("zh-TW"),
            }; 
            app.UseRequestLocalization(new RequestLocalizationOptions()
            { 
                SupportedCultures = supportedCultures,
                SupportedUICultures = supportedCultures,
                //當預設Provider偵測不到用戶支持上述Culture的話,就會是↓
                DefaultRequestCulture = new RequestCulture("zh-CN")//Default UICulture、Culture 
            }); 
            app.UseStaticFiles(); 
            app.UseMvcWithDefaultRoute();
        }
    }
}
接著看資源檔擺放的路徑和命名↓

↑Controller和View使用到的資源檔要分開,沒有加上文化特性名稱的 *.resx(HomeController.resx、Index.resx)就當做是en-US、zh-TW以外共用的語系資源
下面是資源檔的內容,注意存取修飾詞一律選擇「沒有程式碼產生」






然後在HomeController使用IStringLocalizer來讀取資源檔
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Diagnostics;
using Microsoft.AspNetCore.Localization;
using Microsoft.AspNetCore.Mvc;
using Microsoft.Extensions.Localization;
namespace StarterM.Controllers
{
    public class HomeController : Controller
    {
        private readonly IStringLocalizer<HomeController> _localizer;
        public HomeController(IStringLocalizer<HomeController> localizer)
        {
            this._localizer = localizer;
        }
        public IActionResult Index()
        {
             ViewBag.msg = _localizer["Hello"]; 
            //此行測試用
            IRequestCultureFeature rcf = HttpContext.Features.Get<IRequestCultureFeature>();
            return View(rcf);   
        }
         
       
    }
}
以下是Index的View
@using System.Globalization
@using Microsoft.AspNetCore.Localization
@using Microsoft.AspNetCore.Mvc.Localization
@inject IViewLocalizer MyViewLocalizer
@model IRequestCultureFeature 
@{
    Layout = null;
} 
<!DOCTYPE html> 
<html>
<head>
    <meta name="viewport" content="width=device-width" />
    <title>Test 多國語系</title>
</head>
<body>
    <h3>Provider: @Model?.Provider?.GetType()?.Name</h3>
    <ul>
        <li>Culture: @CultureInfo.CurrentCulture </li>
        <li>CultureUI: @CultureInfo.CurrentUICulture</li>
        <li>RequestCulture.Culture: @Model.RequestCulture.Culture</li>
        <li>RequestCulture.UICulture: @Model.RequestCulture.UICulture</li>
        <li>DateTime: @DateTime.Now.ToString() </li>
        <li>Currency: @(10000.ToString("c"))</li>
    </ul>
    <!--以下文字會從資源檔讀取-->
    <div>
        Hello: @ViewBag.msg
    </div>
    <div>
        <h1>CompanyName: @MyViewLocalizer["CompanyName"]</h1>
    </div>
</body>
</html>
使用Postman軟體的執行結果:



最後展示一下QueryString優先權大於AcceptLanguage↓

2018.09.05 追加CookieRequestCultureProvider的範例程式碼
Startup.cs
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Hosting;
using Microsoft.AspNetCore.Http; 
using Microsoft.Extensions.DependencyInjection;
/*引用命名空間*/
using System.Globalization;
using Microsoft.AspNetCore.Mvc.Razor;
using Microsoft.AspNetCore.Localization;
namespace WebApplication1CookieCore
{
    public class Startup
    {
         
        public void ConfigureServices(IServiceCollection services)
        {
            //資源檔所在資料夾
            services.AddLocalization(options => options.ResourcesPath = "Resources");
            services.AddMvc()
                    .AddViewLocalization(LanguageViewLocationExpanderFormat.Suffix);//要使用View多國語系的話就加這行程式碼
        }
         
        public void Configure(IApplicationBuilder app, IHostingEnvironment env)
        {
            if (env.IsDevelopment())
            {
                app.UseDeveloperExceptionPage();
            }
            var supportedCultures = new CultureInfo[] {
                new CultureInfo("en-US"),
                new CultureInfo("zh-TW"),
            };
            app.UseRequestLocalization(new RequestLocalizationOptions()
            {
                SupportedCultures = supportedCultures,
                SupportedUICultures = supportedCultures,
                //當預設Provider偵測不到用戶支持上述Culture的話,就會是↓
                DefaultRequestCulture = new RequestCulture("zh-TW")//Default UICulture、Culture 
            });
            app.UseStaticFiles();
            app.UseMvcWithDefaultRoute();
        }
    }
}
資源檔都和上述一樣,然後是HomeController
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks; 
using Microsoft.AspNetCore.Mvc;
/*引用命名空間*/
using Microsoft.Extensions.Localization;
using Microsoft.AspNetCore.Localization;
using Microsoft.AspNetCore.Mvc.Rendering;
  
namespace WebApplication1CookieCore.Controllers
{
    public class HomeController : Controller
    {
        private readonly IStringLocalizer<HomeController> _localizer;
        public HomeController(IStringLocalizer<HomeController> localizer)
        { 
            this._localizer = localizer;
        }
        public IActionResult Index()
        { 
            ViewBag.msg = _localizer["Hello"];
            //抓出目前使用哪個RequestCulture
            IRequestCultureFeature rcf = HttpContext.Features.Get<IRequestCultureFeature>();
            string c = rcf.RequestCulture.UICulture.Name;
            //下拉選單的option
            List<SelectListItem> options = new List<SelectListItem>() {
                 new SelectListItem(){ Text="zh-TW",Value="zh-TW",Selected=(c=="zh-TW"?true:false) },
                 new SelectListItem(){ Text="en-US",Value="en-US",Selected=(c=="en-US"?true:false) }
            };
            ViewBag.options = options; 
            return View(rcf);
        }
        /// <summary>
        /// 用戶手動切換語系
        /// </summary>
        /// <param name="lang"></param>
        /// <returns></returns>
        [HttpPost]
        public IActionResult ChangeLang(string c)
        {
            string lang = CookieRequestCultureProvider.MakeCookieValue(new RequestCulture(c));
            Response.Cookies.Append(CookieRequestCultureProvider.DefaultCookieName,lang); 
            return RedirectToAction("Index", "Home");//重新導向至Index Action
        }
        //清除cookie中的文化特性
        public IActionResult Clear()
        { 
            //這一行↓
            Response.Cookies.Delete(CookieRequestCultureProvider.DefaultCookieName);
            return Content("Clear OK");    
        }
    }
}
↓Index的View
@using System.Globalization
@using Microsoft.AspNetCore.Localization
@using Microsoft.AspNetCore.Mvc.Localization
@inject IViewLocalizer MyViewLocalizer
@model IRequestCultureFeature
@{
    Layout = null;
}
<!DOCTYPE html>
<html>
<head>
    <meta name="viewport" content="width=device-width" />
    <title>Test 多國語系</title>
</head>
<body> 
    @using (Html.BeginForm("ChangeLang","Home",FormMethod.Post))
    {
        @Html.DropDownList("c", (List<SelectListItem>)ViewBag.options)
        <input type="submit" value="變更語言" />
    }
    <h3>Provider: @Model?.Provider?.GetType()?.Name</h3>
    <ul>
        <li>Culture: @CultureInfo.CurrentCulture </li>
        <li>CultureUI: @CultureInfo.CurrentUICulture</li>
        <li>RequestCulture.Culture: @Model.RequestCulture.Culture</li>
        <li>RequestCulture.UICulture: @Model.RequestCulture.UICulture</li>
        <li>DateTime: @DateTime.Now.ToString() </li>
        <li>Currency: @(10000.ToString("c"))</li>
    </ul>
 
    <!--以下文字會從資源檔讀取-->
    <div>
        Hello: @ViewBag.msg
    </div>
    <div>
        <h1>CompanyName: @MyViewLocalizer["CompanyName"]</h1>
    </div>
</body>
</html>
執行結果↓


手動切換回中文語系↓

結語
最後提供一個小技巧
本文的預設文化特性使用 DefaultRequestCulture = new RequestCulture("zh-CN"),其實是為了Demo才這樣寫
實務上的話,我會從zh-TW或en-US挑其中一種當做預設值↓ (因為早已準備好zh-TW、en-US兩種資源檔)
DefaultRequestCulture = new RequestCulture("en-US")
這樣有個好處,就不用再多準備通用語系資源檔 *.resx,Index.resx和HomeController.resx這兩個檔案就可以刪除啦~