ASP.NET Core 中文文檔 第三章 原理(6)全球化與本地化


原文:Globalization and localization
作者:Rick AndersonDamien BowdenBart CalixtoNadeem Afana
翻譯:謝煬(Kiler)
校對:許登洋(Seay)高嵩

使用 ASP.NET Core 創建一個多語言版本的網站有助於你吸引到更多的用戶,ASP.NET Core 提供服務和中間件來支持本地化語言和文化。

國際化涉及 全球化 和 本地化。全球化是為了應用程序支持不同文化而設計的。全球化增加了對特定地理區域的語言文字的輸入、顯示和輸出的支持。

本地化是針對一個特定的文化/區域,你早已經完成了本地化處理的全球化應用程序。欲了解更多信息,請參閱文檔尾部的 Globalization and localization terms 。

應用程序本地化包含以下內容:

  1. 讓應用程序的內容本地化。
  2. 為不同的文化和語言提供本地化資源包。
  3. 在每個請求中實現語言/文化切換策略。

讓應用程序的內容本地化

在 ASP.NET Core 中, IStringLocalizer
 以及 IStringLocalizer
 的架構在開發本地化應用程序時為提高生產力的手段。IStringLocalizer  使用 ResourceManager 和 ResourceReader 在運行時提供指定文化的資源文件。IStringLocalizer 是一個實現了 IEnumerable 的簡單接口並且擁有索引器來來返回本地化的字符串。IStringLocalizer 並不需要你把默認語言字符串存儲在資源文件中。你可以針對某個特定的語言開發應用程序,而不是需要在開發早期創建資源文件。下面的代碼演示了如何包裝字符串 “About Title” 本地化。

using Microsoft.AspNet.Mvc;
using Microsoft.Extensions.Localization;

namespace Localization.StarterWeb.Controllers
{
    [Route("api/[controller]")]
    public class AboutController : Controller
    {
        private readonly IStringLocalizer<AboutController> _localizer;

        public AboutController(IStringLocalizer<AboutController> localizer)
        {
            _localizer = localizer;
        }

        [HttpGet]
        public string Get()
        {
            return _localizer["About Title"];
        }
    }
}

在上面的代碼中,IStringLocalizer<T> 實現了 Dependency Injection。如果沒有發現 “About Title” 的本地化值,則索引的鍵值被返回,即是字符串 “About Title” 。你可以在應用程序中保留默認語言文字字符串,然后再使用 localizer 包裝他們,這樣你就可以專注於開發應用程序。使用默認語言開發應用並為進行本地化的步驟做准備,同時無需事先創建一個默認的資源文件。另外,你也可以使用傳統的方法,一鍵恢復默認語言的字符串。對於大部分開發者來說新的工作流程無需一個默認語言的 .resx 文件,並且簡單地包裝字符串可以減少本地化的應用程序的工作量。其他開發者會選擇傳統的工作流程,因為它可以更容易地與長字符串文字工作,並使其更易於更新本地化字符串。

使用 IHtmlLocalizer  來處理包含 HTML 的資源文件, IHtmlLocalizer 對格式化過的資源字符串參數進行編碼,而不是對原始資源字符串。下面例子中的高亮代碼,只有 name 參數的值被 HTML 編碼。

using System;
using Microsoft.AspNet.Http;
using Microsoft.AspNet.Localization;
using Microsoft.AspNet.Mvc;
using Microsoft.AspNet.Mvc.Localization;

namespace Localization.StarterWeb.Controllers
{
    public class BookController : Controller
    {
        private readonly IHtmlLocalizer<BookController> _localizer;

        public BookController(IHtmlLocalizer<BookController> localizer)
        {
            _localizer = localizer;
        }

        public IActionResult Hello(string name)
        {
            ViewData["Message"] = _localizer["<b>Hello</b><i> {0}</i>", name];

            return View();
        }

提示:你通常只想本地化文本而非HTML。

在最低層次, 你可以通過 Dependency Injection 獲取 IStringLocalizerFactory

 public class TestController : Controller
 {
     private readonly IStringLocalizer _localizer;
     private readonly IStringLocalizer _localizer2;

     public TestController(IStringLocalizerFactory factory)
     {
         _localizer = factory.Create(typeof(SharedResource));
         _localizer2 = factory.Create("SharedResource", location: null);
     }       

     public IActionResult About()
     {
         ViewData["Message"] = _localizer["Your application description page."] 
             + " loc 2: " + _localizer2["Your application description page."];

         return View();
     }  

上面的代碼演示了兩個不同的工廠創建方法。

你可以通過控制器、區域划分本地化字符串,或者都包含在一個容器中。在示例應用程序中,名為 SharedResource 的 dummy 類被用於共享資源。

// Dummy class to group shared resources

namespace Localization.StarterWeb
{
    public class SharedResource
    {
    }
}

一些開發人員使用 Startup 類來包含全局或共享字符串。在下面的例子中,展示了 InfoController 以及 SharedResource 本地化使用:

 public class InfoController : Controller
 {
     private readonly IStringLocalizer<InfoController> _localizer;
     private readonly IStringLocalizer<SharedResource> _sharedLocalizer;

     public InfoController(IStringLocalizer<InfoController> localizer,
                    IStringLocalizer<SharedResource> sharedLocalizer)
     {
         _localizer = localizer;
         _sharedLocalizer = sharedLocalizer;
     }

     public string TestLoc()
     {
         string msg = "Shared resx: " + _sharedLocalizer["Hello!"] +
                      " Info resx " + _localizer["Hello!"];
         return msg;
     }

視圖本地化

IViewLocalizer 服務為視圖提供本地化字符串。 ViewLocalizer 類實現了這個接口,並且根據視圖文件的路徑來查找資源。下面的代碼演示了如何使用 IViewLocalizer 的默認實現:

@using Microsoft.AspNet.Mvc.Localization

@inject IViewLocalizer Localizer

@{
    ViewData["Title"] = Localizer["About"];
}
<h2>@ViewData["Title"].</h2>
<h3>@ViewData["Message"]</h3>

<p>@Localizer["Use this area to provide additional information."]</p>

IViewLocalizer 的默認實現基於視圖的文件名稱來查找資源文件。沒有使用全局共享的資源文件的可選項。 ViewLocalizer 使用 IHtmlLocalizer 實現本地化,所以 Razor 模版不會 HTML 編碼本地化字符串。你可以使用參數傳遞資源字符串並且 IViewLocalizer 會HTML 編碼參數,而不是資源字符串。參考下面的 Razor 標簽代碼:

@Localizer["<i>Hello</i> <b>{0}!</b>", UserManager.GetUserName(User)]

法語資源文件會包含如下內容:

< i>Hello< /i > < b>{0}!< /b> < i>Bonjour< /i > < b>{0}!< /b >

渲染視圖將包含資源文件中的 HTML 標簽。

提示:你通常只想本地化文本而非HTML。

為了在視圖中試用共享資源文件,需要注入 IHtmlLocalizer

@using Microsoft.AspNet.Mvc.Localization
@using Localization.StarterWeb.Services

@inject IViewLocalizer Localizer
@inject IHtmlLocalizer<SharedResource> SharedLocalizer

@{
    ViewData["Title"] = Localizer["About"];
}
<h2>@ViewData["Title"].</h2>

<h1>@SharedLocalizer["Hello!"]</h1>

DataAnnotations 本地化

使用 IStringLocalizer  本地化 DataAnnotations 錯誤信息。 使用選項ResourcesPath = "Resources", RegisterViewModel 中的錯誤信息會存儲到以下路徑中:

  • Resources/ViewModels.Account.RegisterViewModel.fr.resx
  • Resources/ViewModels/Account/RegisterViewModel.fr.resx
 public class RegisterViewModel
 {
     [Required(ErrorMessage = "The Email field is required.")]
     [EmailAddress(ErrorMessage = "The Email field is not a valid e-mail address.")]
     [Display(Name = "Email")]
     public string Email { get; set; }

     [Required(ErrorMessage = "The Password field is required.")]
     [StringLength(8, ErrorMessage = "The {0} must be at least {2} characters long.", MinimumLength = 6)]
     [DataType(DataType.Password)]
     [Display(Name = "Password")]
     public string Password { get; set; }

     [DataType(DataType.Password)]
     [Display(Name = "Confirm password")]
     [Compare("Password", ErrorMessage = "The password and confirmation password do not match.")]
     public string ConfirmPassword { get; set; }
 }

運行時不支持從非驗證屬性中查找本地化字符串。在上面的代碼里,“Email”(來自 [Display(Name = "Email")])將不會被本地化。

為你的語言和文化提供本地化資源支持

SupportedCultures(文化支持) 以及 SupportedUICultures(UI文化支持)

ASP.NET Core 允許你指定兩個文化值, SupportedCultures 以及SupportedUICulturesSupportedCultures 的 CultureInfo 對象決定了和文化相關的函數,如日期,時間,數字和貨幣格式的結果。 SupportedCultures 同時決定了文字如何排序,大小寫轉換以及字符串比較。參考CultureInfo.CurrentCulture 獲取更多關於服務器如何獲取文化的信息。SupportedUICultures 決定如何通過 ResourceManager 查找翻譯字符串(從 .resx 文件)。 ResourceManager
 只是通過 CurrentUICulture 簡單的查找指定文化的字符串。.NET 的每個線程都會擁有 CurrentCulture 和CurrentUICulture 對象。當 ASP.NET Core 在渲染與文化相關的函數的時候會檢視這些對象值。例如,如果當前線程的區域性設置為 “en-US” (英語、美國), DateTime.Now.ToLongDateString() "Thursday, February 18, 2016" ,但如果 CurrentCulture 設置為 “es-ES”(西班牙語、西班牙),輸出將會是 “jueves, 18 de febrero de 2016”。

使用資源文件

資源文件是一種從代碼中分離本地化字符串的有效機制。非默認語言翻譯字符串被隔離到 .resx 資源文件中。例如,你可能希望創建一個名為Welcome.es.resx 的西班牙語資源文件來包含翻譯字符串。“es”是西班牙語的語言編碼。在Visual Studio中,這樣創建資源文件:

  1. 在 Solution Explorer 中,右擊包含資源文件的目錄 > Add > New Item 。
    newi.png

  2. 在 Search installed templates 對話框中, 輸入 “resource” 並且命名文件。
    res.png

  3. 在 Name 列輸入鍵值(本地字符串)在 Value 列輸入翻譯值。

hola.png

Visual Studio 展示出 Welcome.es.resx 文件。
se.png

使用 Visual Studio 創建資源文件

如果你在 Visual Studio 中創建一個資源文件,而且文件名中不存在文化信息(例如,Welcome.resx),Visual Studio 將會為之創建一個 C# 類並且為每個字符串創建一個字段。這通常不是你想要的 ASP.NET Core 的方式;通常你不會有一個默認的 .resx 資源文件(文件名不包含文化信息的 .resx 文件)。我們建議你創建帶有文化名稱的 .resx(例如:Welcome.fr.resx )文件。當你創建一個與文化信息關聯的 .resx 時,Visual Studio 不會產生類文件。按照我們預期大部分開發者 不會 創建默認語言資源文件。

添加其他文化

每一種語言和文化的結合(除了默認語言外)需要一個獨特的資源文件。你可以為不同的文化和語言環境創建新的資源文件,ISO 語言代碼作為文件名的一部分(例如, en-us 、 fr-ca 以及 en-gb)。這些 ISO 語言代碼放置在文件名和 .resx 文件擴展名之間,如 Welcome.es-MX.resx (西班牙語/墨西哥)。要指定文化中性語言,你可以消除國家代碼,如 Welcome.fr.resx 為法語。

為每個請求提供語言文化選擇功能的實施策略

配置本地化

在 ConfigureServices 方法中本地化的配置:

  public void ConfigureServices(IServiceCollection services)
  {

      services.AddLocalization(options => options.ResourcesPath = "Resources");

      services.AddMvc()
        .AddViewLocalization(LanguageViewLocationExpanderFormat.Suffix)
        .AddDataAnnotationsLocalization();
  • AddLocalization 在服務容器中添加本地化服務。上述代碼同時把資源文件路徑設置到 “Resources”。
  • AddViewLocalization 添加本地化視圖文件支持。在這個示例中視圖本地化是基於視圖文件后綴的。例如: Index.fr.cshtml 中的 “fr”。
  • AddDataAnnotationsLocalization 增加了通過 IStringLocalizer 來抽象支持本地化 DataAnnotations 驗證消息。

本地化中間件

在請求中的當前的文化是在本地化 Middleware 中設置的。本地化中間件在Startup.cs 文件的 Configure 方法中啟用。

  public void Configure(IApplicationBuilder app, IHostingEnvironment env, ILoggerFactory loggerFactory)

      var supportedCultures = new[]
      {
          new CultureInfo("en-US"),
          new CultureInfo("en-AU"),
          new CultureInfo("en-GB"),
          new CultureInfo("en"),
          new CultureInfo("es-ES"),
          new CultureInfo("es-MX"),
          new CultureInfo("es"),
          new CultureInfo("fr-FR"),
          new CultureInfo("fr"),
      };

      app.UseRequestLocalization(new RequestLocalizationOptions
      {
          DefaultRequestCulture = new RequestCulture("en-US"),
          // Formatting numbers, dates, etc.
          SupportedCultures = supportedCultures,
          // UI strings that we have localized.
          SupportedUICultures = supportedCultures
      });

      // Remaining code omitted for brevity.

UseRequestLocalization 初始化一個 RequestLocalizationOptions 對象。在每次請求里 RequestLocalizationOptions 的 RequestCultureProvider 列表會被遍歷,第一個provider 會被使用來判斷請求使用的文化。默認的 provider 來自RequestLocalizationOptions 類:

  1. QueryStringRequestCultureProvider
  2. CookieRequestCultureProvider
  3. AcceptLanguageHeaderRequestCultureProvider

默認列表從最具體的到最不具體的。后面的文章中我會告訴你如何更改順序,甚至添加自定義的本地化 provider。如果沒有非空的 provider,DefaultRequestCulture 被使用。

QueryStringRequestCultureProvider

有些應用程序會使用一個查詢字符串來設置 區域性和 UI 區域性。對於使用 cookie 或者 Accept-Language 頭的方法的應用程序,在 URL 上增加查詢字符串有助於調試和測試代碼。除非你修改 RequestCultureProvider 列表,否則查詢字符串參數永遠是用來指定本地化 provider 的。你可在查詢字符串參數中傳遞 culture 以及 ui-culture 參數。下面的例子指定了具體的區域性(語言和區域)設置為西班牙語/墨西哥:

http://localhost:5000/?culture=es-MX&ui-culture=es-MX

如果你僅僅使用(culture 或者 ui-culture)中的一個參數進行傳遞,查詢字符串 provider 將使用你傳遞一個值來設置這兩個參數。例如,僅設置culture,將會同樣設置 Culture 和 UICulture

http://localhost:5000/?culture=es-MX

CookieRequestCultureProvider

生產環境的應用程序通常會提供一種機制,把區域性信息設置到 ASP.NET Core 區域性 cookie 之上。使用 MakeCookieValue 方法創建一個 cookie。

CookieRequestCultureProvider 的 DefaultCookieName 返回用於跟蹤用戶的首選區域性信息默認的 Cookie 名稱。默認的 Cookie 名稱是 “.AspNetCore.Culture”。

cookie 的格式是 c=%LANGCODE%|uic=%LANGCODE%c 為區域信息 和 uic 為 UI 區域信息,例如:

c=’en-UK’|uic=’en-US’

如果僅指定 culture 或 UI culture中的一個,指定的區域性信息將同時用於 culture和 UI culture。

HTTP Accept-Language HTTP 頭信息

大多數瀏覽器支持設置 Accept-Language header 頭信息,這個設置最初的目的是為了指定用戶的語言。指示什么類型的瀏覽器已被設置且發送或已經從底層操作系統繼承。從瀏覽器請求的 Accept-Language HTTP 標頭來檢測用戶的首選語言容易產生錯誤(請參見 在瀏覽器中設置語言首選項)。生產環境中應用程序應該包括一種方法讓用戶自己選擇的區域性信息。

在 IE 瀏覽器中設置 Accept-Language HTTP 頭信息

  1. 在齒輪圖標菜單中, 點擊 Internet Options

  2. 點擊 Languages
    ../_images/lang.png

  3. 點擊 Set Language Preferences

  4. 點擊 Add a language

  5. 添加語言。

  6. 點擊語言, 然后點擊 Move Up

使用自定義 provider

假設你想要在數據庫里面存儲客戶的語言和文化信息。你可以寫一個 provider 來查找這些用戶的值。下面的代碼演示如何添加自定義 provider:

services.Configure<RequestLocalizationOptions>(options =>
{
    var supportedCultures = new[]
    {
        new CultureInfo("en-US"),
        new CultureInfo("fr")
    };

    options.DefaultRequestCulture = new RequestCulture(culture: "en-US", uiCulture: "en-US");
    options.SupportedCultures = supportedCultures;
    options.SupportedUICultures = supportedCultures;

    options.RequestCultureProviders.Insert(0, new CustomRequestCultureProvider(async context =>
    {
       // My custom request culture logic
      return new ProviderCultureResult("en");
    }));
});

使用 RequestLocalizationOptions 添加或者刪除本地化 providers。

資源文件命名

資源被命名為資源文件的類名減去默認命名空間(一般是應用程序集名稱)。例如 LocalizationWebsite.Web 項目 LocalizationWebsite.Web.Startup 類的法語的資源將被命名為 Startup.fr.resx

LocalizationWebsite.Web.Controllers.HomeController 類則是Controllers.HomeController.fr.resx。如果出於某種原因,你的目標類是不是在默認的命名空間,你將需要完整的類型名稱。 例如,在示例項目類型 ExtraNamespace.Tools 類會使用 ExtraNamespace.Tools.fr.resx 。

在示例項目中, ConfigureServices 方法將 ResourcesPath 設置為 “Resources”,所以對於 home controller 的法語資源文件的項目相對路徑是 Resources/Controllers.HomeController.fr.resx。另外,你也可以使用文件夾來組織資源文件。對於 home controller ,路徑將是Resources/Controllers/HomeController.fr.resx。如果不使用 ResourcesPath 可選項 , .resx 文件會包含在項目的根目錄。 HomeController 的資源文件將被命名為 Controllers.HomeController.fr.resx。選擇使用點或路徑命名約定的選擇取決於你想如何組織你的資源文件。

資源文件 點或路徑命名
Resources/Controllers.HomeController.fr.resx
Resources/Controllers/HomeController.fr.resx 路徑

資源文件在 Razor 視圖中使用類似 @inject IViewLocalizer 模式來調用。對於視圖的資源文件可以使用點命名或路徑命名的方式進行命名。Razor 視圖資源文件名參照其關聯視圖文件路徑。假設我們設置 ResourcesPath 為 “Resources”,關聯視圖 Views/Book/About.cshtml 的法語資源文件將會如下所示:

  • Resources/Views/Home/About.fr.resx
  • Resources/Views.Home.About.fr.resx

如果你不設置 ResourcesPath 選項,視圖的 .resx 文件將與視圖文件位於同一文件夾內。

如果你刪除了 “.fr” 區域性標志但是你又把當前區域性信息設置為法語(通過 Cookie 或其他機制),默認的資源文件將會被讀取出來用以字符串本地化。當在你的服務器上找不到對應你的請求區域性信息的資源文件的時候,資源管理器會指定指定默認或備份資源。如果你想在缺少資源請求的文化時能返回鍵值,你不能有一個默認的資源文件。

編程方式設置文化

GitHub 上的示例項目 Localization.StarterWeb 包含用戶界面來設置 CultureViews/Shared/_SelectLanguagePartial.cshtml 文件允許你從支持的區域性列表中選擇區域:

@using Microsoft.AspNet.Builder
@using Microsoft.AspNet.Http.Features
@using Microsoft.AspNet.Localization
@using Microsoft.AspNet.Mvc.Localization
@using Microsoft.Extensions.Options

@inject IViewLocalizer Localizer
@inject IOptions<RequestLocalizationOptions> LocOptions

@{
    var requestCulture = Context.Features.Get<IRequestCultureFeature>();
    var cultureItems = LocOptions.Value.SupportedUICultures
        .Select(c => new SelectListItem { Value = c.Name, Text = c.DisplayName })
        .ToList();
}

<div title="@Localizer["Request culture provider:"] @requestCulture?.Provider?.GetType().Name">
    <form id="selectLanguage" asp-controller="Home" 
          asp-action="SetLanguage" asp-route-returnUrl="@Context.Request.Path" 
          method="post" class="form-horizontal" role="form">
        @Localizer["Language:"] <select name="culture"
          asp-for="@requestCulture.RequestCulture.UICulture.Name" asp-items="cultureItems">
        </select>
    </form>
</div>

Views/Shared/_SelectLanguagePartial.cshtml 文件添加到布局文件的 footer
區域,因此將提供給所有視圖使用:

  <div class="container body-content">
      @RenderBody()
      <hr />
      <footer>
          <div class="row">
              <div class="col-md-6">
                  <p>&copy; 2015 - Localization.StarterWeb</p>
              </div>
              <div class="col-md-6 text-right">
                  @await Html.PartialAsync("_SelectLanguagePartial")
              </div>
          </div>
      </footer>
  </div>

SetLanguage 方法設置文化 cookie。

  [HttpPost]
  public IActionResult SetLanguage(string culture, string returnUrl)
  {
      Response.Cookies.Append(
          CookieRequestCultureProvider.DefaultCookieName,
          CookieRequestCultureProvider.MakeCookieValue(new RequestCulture(culture)),
          new CookieOptions { Expires = DateTimeOffset.UtcNow.AddYears(1) }
      );

      return LocalRedirect(returnUrl);
  }

你不能簡單的把 _SelectLanguagePartial.cshtml 應用到示例代碼項目。在GitHub 上 Localization.StarterWeb 項目有相關代碼通過依賴注入容器把 RequestLocalizationOptions 注入到 Razor 局部模版。

本地化全球化術語

本地化你的應用程序的過程也需要對現代軟件開發中常用的相關字符集的有一個基本的了解,並熟悉與之相關的問題。盡管所有的計算機把文本存儲為數字(編碼),不同的系統使用不同的數字存儲相同的文本。本地化進程是指代通過指定的文化/區域設置來翻譯應用程序的用戶界面(UI)。

Localizability 是用於驗證一個全球化的應用程序已經准備好本地化的一個即時流程。

區域性名稱的 RFC 4646 格式為 “ -<country/regioncode2>” ,其中 是語言代碼, <country/regioncode2> 是子文化代碼。例如, es-CL 西班牙語(智利),  en-US 是指 英語(美國),  en-AU則是英語(澳大利亞)。  RFC 4646 是用與語言相關的ISO 639雙字母小寫區域性代碼和一個與國家或地區相關的ISO3166雙字母大寫子代碼組合。詳見 Language Culture Name

國際化通常縮寫為 “I18N”。縮寫采取首字母和尾字母以及它們之間的字母數,所以 18 代表首字母 “I” 和尾字母 “N” 之間的字母數。這同樣適用於全球化(G11N)和本地化(L10N)。

術語:

  • Globalization(全球化) (G11N):讓你的應用程序支持多種語言和區域設置。
  • Localization(本地化) (L10N):讓你的應用程序支持某一種特定語言/區域設置。
  • Internationalization(國際化) (I18N):是全球化和本地化的結合。
  • Culture(文化):指代語言和可選地區。
  • Neutral culture(非特定區域文化):指代某種語言,不包含區域。(如 “en”、”es”)
  • Specific culture(特定區域文化):指代某種語言和區域的組合。(如 “en-US”、”en-GB”、”es-CL”)
  • Locale(區域設置):區域設置和文件是相同的。

附錄資源


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM