[ASP.net Core] 多國語系 Part 1 初探篇

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這兩個檔案就可以刪除啦~