.NET 中的當地語系化

當地語系化是一種程序,可將應用程式資源翻譯為應用程式所支援之每個文化特性的當地語系化版本。 只有在完成可當地語系化檢閱步驟,確認全球化的應用程式已準備好進行當地語系化之後,才能繼續進行當地語系化步驟。

準備好進行當地語系化的應用程式分成兩個概念性區塊,其中一個區塊包含所有使用者介面元素,另一個區塊則包含可執行檔程式碼。 使用者介面區塊包含只可當地語系化的使用者介面元素,例如字串、錯誤訊息、對話方塊、功能表、內嵌物件資源,以及適用於中性文化特性的其他元素。 程式碼區塊僅包含可供所有支援的文化特性使用的應用程式程式碼。 Common Language Runtime 支援附屬組件資源模型,可將應用程式的可執行檔程式碼與其資源分開。 如需有關實作此模型的詳細資訊,請參閱 .NET 中的資源

針對每個當地語系化版本的應用程式,加入新的附屬組件,其中包含轉譯成適用於目標文化特性之語言的當地語系化使用者介面區塊。 所有文化特性的程式碼區塊應該維持不變。 當地語系化版本的使用者介面區塊結合程式碼區塊之後,會產生您應用程式的當地語系化版本。

在本文中,您將瞭解如何使用 IStringLocalizer<T>IStringLocalizerFactory 的實作。 本文中的所有範例原始程式碼都需要使用 Microsoft.Extensions.LocalizationMicrosoft.Extensions.Hosting NuGet 套件。 如需關於提供主機服務的詳細資訊,請參閱 .NET 一般主機

資源檔

隔離可當地語系化字串的主要機制是使用資源檔。 資源檔是副檔名為 .resx 的 XML 檔案。 資源檔在取用應用程式執行前會先翻譯,換句話說,它們代表待用的已翻譯內容。 資源檔名稱通常包含地區設定識別碼,並採用下列格式:

<FullTypeName><.Locale>.resx

其中:

  • <FullTypeName> 代表特定類型的可當地語系化資源。
  • 選擇性的 <.Locale> 表示資源檔內容的地區設定。

指定地區設定

地區設定應該至少定義語言,但它也可以定義文化 (區域語言),甚至是國家或地區。 這些區段通常以 - 字元分隔。 隨著文化特性的精確性增加,可以利用「文化特性後援」規則來優先處理最符合的項目。 地區設定應該對應至已知的語言標籤。 如需詳細資訊,請參閱CultureInfo.Name

文化特性後援案例

假設您的當地語系化應用程式支援不同的塞爾維亞地區設定,而且其 MessageService 具有下列資源檔:

檔案 區域語言 國碼 (地區碼)
MessageService.sr-Cyrl-RS.resx (斯拉夫文,塞爾維亞) RS
MessageService.sr-Cyrl.resx 古斯拉夫文
MessageService.sr-Latn-BA.resx (拉丁文,波士尼亞與赫塞哥維納) 波士尼亞與赫塞哥維納
MessageService.sr-Latn-ME.resx (拉丁文,蒙特內哥羅) ME
MessageService.sr-Latn-RS.resx (拉丁文,塞爾維亞) RS
MessageService.sr-Latn.resx 拉丁文
MessageService.sr.resx 拉丁文
MessageService.resx

語言的預設區域語言。

當您的應用程式在執行時,其 CultureInfo.CurrentCulture 設定文化特性為 "sr-Cyrl-RS",當地語系化會嘗試依下列順序解析檔案:

  1. MessageService.sr-Cyrl-RS.resx
  2. MessageService.sr-Cyrl.resx
  3. MessageService.sr.resx
  4. MessageService.resx

不過,如果您的應用程式在執行時,其 CultureInfo.CurrentCulture 設定文化特性為 "sr-Latn-BA",當地語系化則會嘗試依下列順序解析檔案:

  1. MessageService.sr-Latn-BA.resx
  2. MessageService.sr-Latn.resx
  3. MessageService.sr.resx
  4. MessageService.resx

當沒有任何對應的相符項目時,「文化特性後援」規則會忽略地區設定。這表示如果找不到相符項目,則會選取資源檔 4 號。 如果文化特性設定為 "fr-FR",當地語系化最終會根據 MessageService.resx 檔案執行,而這可能會造成問題。 如需詳細資訊,請參閱資源後援流程

資源查閱

在查閱的過程中,資源檔會自動解析。 如果您的專案檔名稱與專案的根命名空間不同,元件名稱就可能不同。 這會導致資源查閱失敗。 若要解決此不相符的問題,請使用 RootNamespaceAttribute 來提示當地語系化服務。 提供後,它可以在資源查閱期間使用。

此專案範例名為 example.csproj,它會建立 example.dllexample.exe ,不過會使用 Localization.Example 命名空間。 請套用 assembly 等級屬性來更正此不相符問題:

[assembly: RootNamespace("Localization.Example")]

註冊當地語系化服務

若要註冊當地語系化服務,請在服務設定期間呼叫其中一個 AddLocalization 擴充方法。 這麼做會啟用以下類型的相依性插入 (DI):

設定當地語系化選項

AddLocalization(IServiceCollection, Action<LocalizationOptions>) 多載接受類型為 Action<LocalizationOptions>setupAction 參數。 這可讓您設定當地語系化選項。

HostApplicationBuilder builder = Host.CreateApplicationBuilder(args);

builder.Services.AddLocalization(options =>
{
    options.ResourcesPath = "Resources";
});

// Omitted for brevity.

資源檔可以存在於專案中的任何地方,但有一些已實證過可成功的常見做法。 所使用的方法通常都是阻力最少的方法。 在上述 C# 程式碼中:

這會讓當地語系化服務在資源目錄中尋找資源檔。

使用 IStringLocalizer<T>IStringLocalizerFactory

註冊 (並選擇性地設定) 當地語系化服務之後,您可以用 DI 使用下列類型:

若要建立能傳回當地語系化字串的訊息服務,請考慮下列 MessageService

using System.Diagnostics.CodeAnalysis;
using Microsoft.Extensions.Localization;

namespace Localization.Example;

public sealed class MessageService(IStringLocalizer<MessageService> localizer)
{
    [return: NotNullIfNotNull(nameof(localizer))]
    public string? GetGreetingMessage()
    {
        LocalizedString localizedString = localizer["GreetingMessage"];

        return localizedString;
    }
}

在上述 C# 程式碼中:

  • 已宣告 IStringLocalizer<MessageService> localizer 欄位。
  • 主要建構函式會定義 IStringLocalizer<MessageService> 參數,並將其擷取為 localizer 引數。
  • 方法 GetGreetingMessage 會叫用 IStringLocalizer.Item[String],以傳遞 "GreetingMessage" 做為引數。

IStringLocalizer 也支援參數化字串資源,其中,請考慮下列 ParameterizedMessageService

using System.Diagnostics.CodeAnalysis;
using Microsoft.Extensions.Localization;

namespace Localization.Example;

public class ParameterizedMessageService(IStringLocalizerFactory factory)
{
    private readonly IStringLocalizer _localizer =
        factory.Create(typeof(ParameterizedMessageService));

    [return: NotNullIfNotNull(nameof(_localizer))]
    public string? GetFormattedMessage(DateTime dateTime, double dinnerPrice)
    {
        LocalizedString localizedString = _localizer["DinnerPriceFormat", dateTime, dinnerPrice];

        return localizedString;
    }
}

在上述 C# 程式碼中:

  • 已宣告 IStringLocalizer _localizer 欄位。
  • 主要建構函式會接受 IStringLocalizerFactory 參數 (用來根據 ParameterizedMessageService 型別建立 IStringLocalizer),並將它指派給 _localizer 欄位。
  • 方法 GetFormattedMessage 會叫用 IStringLocalizer.Item[String, Object[]],以傳遞 "DinnerPriceFormat"dateTime 物件和 dinnerPrice 作為引數。

重要

不需要 IStringLocalizerFactory。 相反地,取用服務最好要求 IStringLocalizer<T>

兩種 IStringLocalizer.Item[] 索引子都會傳回具有 隱含轉換string?LocalizedString

組合在一起

若要舉例說明同時使用這兩種訊息服務以及本地化和資源文件的應用程式,請考慮以下 Program.cs 文件:

using System.Globalization;
using Localization.Example;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;
using Microsoft.Extensions.Localization;
using Microsoft.Extensions.Logging;
using static System.Console;
using static System.Text.Encoding;

[assembly: RootNamespace("Localization.Example")]

OutputEncoding = Unicode;

if (args is [var cultureName])
{
    CultureInfo.CurrentCulture =
        CultureInfo.CurrentUICulture =
            CultureInfo.GetCultureInfo(cultureName);
}

HostApplicationBuilder builder = Host.CreateApplicationBuilder(args);

builder.Services.AddLocalization();
builder.Services.AddTransient<MessageService>();
builder.Services.AddTransient<ParameterizedMessageService>();
builder.Logging.SetMinimumLevel(LogLevel.Warning);

using IHost host = builder.Build();

IServiceProvider services = host.Services;

ILogger logger =
    services.GetRequiredService<ILoggerFactory>()
        .CreateLogger("Localization.Example");

MessageService messageService =
    services.GetRequiredService<MessageService>();
logger.LogWarning(
    "{Msg}",
    messageService.GetGreetingMessage());

ParameterizedMessageService parameterizedMessageService =
    services.GetRequiredService<ParameterizedMessageService>();
logger.LogWarning(
    "{Msg}",
    parameterizedMessageService.GetFormattedMessage(
        DateTime.Today.AddDays(-3), 37.63));

await host.RunAsync();

在上述 C# 程式碼中:

每個 *MessageService 類別都會定義一組 .resx 檔案,其中每個檔案都只有一個項目。 以下是 MessageService 資源檔的內容範例 (從 MessageService.resx 開始):

<?xml version="1.0" encoding="utf-8"?>
<root>
  <data name="GreetingMessage" xml:space="preserve">
    <value>Hi friends, the ".NET" developer community is excited to see you here!</value>
  </data>
</root>

MessageService.sr-Cyrl-RS.resx

<?xml version="1.0" encoding="utf-8"?>
<root>
  <data name="GreetingMessage" xml:space="preserve">
    <value>Здраво пријатељи, ".NЕТ" девелопер заједница је узбуђена што вас види овде!</value>
  </data>
</root>

MessageService.sr-Latn.resx:

<?xml version="1.0" encoding="utf-8"?>
<root>
  <data name="GreetingMessage" xml:space="preserve">
    <value>Zdravo prijatelji, ".NET" developer zajednica je uzbuđena što vas vidi ovde!</value>
  </data>
</root>

以下是 ParameterizedMessageService 資源檔的內容範例 (從 ParameterizedMessageService.resx 開始):

<?xml version="1.0" encoding="utf-8"?>
<root>
  <data name="DinnerPriceFormat" xml:space="preserve">
    <value>On {0:D} my dinner cost {1:C}.</value>
  </data>
</root>

ParameterizedMessageService.sr-Cyrl-RS.resx:

<?xml version="1.0" encoding="utf-8"?>
<root>
  <data name="DinnerPriceFormat" xml:space="preserve">
    <value>У {0:D} моја вечера је коштала {1:C}.</value>
  </data>
</root>

ParameterizedMessageService.sr-Latn.resx:

<?xml version="1.0" encoding="utf-8"?>
<root>
  <data name="DinnerPriceFormat" xml:space="preserve">
    <value>U {0:D} moja večera je koštala {1:C}.</value>
  </data>
</root>

提示

為了簡潔起見,所有資源檔的 XML 註解、結構描述和 <resheader> 元素皆已省略。

執行範例

下列執行範例會顯示不同目標地區設定的當地語系化輸出。

考慮 "sr-Latn"

dotnet run --project .\example\example.csproj sr-Latn

warn: Localization.Example[0]
      Zdravo prijatelji, ".NET" developer zajednica je uzbuđena što vas vidi ovde!
warn: Localization.Example[0]
      U utorak, 03. avgust 2021. moja večera je koštala 37,63 ¤.

當省略給 .NET CLI 的引數以執行專案時,會使用預設的系統文化特性 (在此範例中為 "en-US"):

dotnet run --project .\example\example.csproj

warn: Localization.Example[0]
      Hi friends, the ".NET" developer community is excited to see you here!
warn: Localization.Example[0]
      On Tuesday, August 3, 2021 my dinner cost $37.63.

傳遞 "sr-Cryl-RS" 時,會找到正確的對應資源檔並套用當地語系化:

dotnet run --project .\example\example.csproj sr-Cryl-RS

warn: Localization.Example[0]
      Здраво пријатељи, ".NЕТ" девелопер заједница је узбуђена што вас види овде!
warn: Localization.Example[0]
      У уторак, 03. август 2021. моја вечера је коштала 38 RSD.

應用程式範例不會提供 "fr-CA" 的資源檔,但用該文化特性進行呼叫時,會使用非當地語系化的資源檔。

警告

由於只找到文化特性但未找到正確的資源檔,因此套用格式設定時,只能實現部分當地語系化:

dotnet run --project .\example\example.csproj fr-CA

warn: Localization.Example[0]
     Hi friends, the ".NET" developer community is excited to see you here!
warn: Localization.Example[0]
     On mardi 3 août 2021 my dinner cost 37,63 $.

另請參閱