Lokalisierung in .NET

Unter Lokalisierung versteht man den Vorgang, bei dem Anwendungsressourcen in lokalisierte Versionen für sämtliche von der Anwendung unterstützten Kulturen übersetzt werden. Sie sollten erst nach Durchführung des Schritts Überprüfung der Lokalisierbarkeit mit dem Lokalisierungsschritt fortfahren, um sicherzustellen, dass die globalisierte Anwendung für die Lokalisierung bereit ist.

Eine zur Lokalisierung bereitstehende Anwendung wird in zwei grundlegende Blöcke unterteilt: einen Block, der alle Elemente der Benutzeroberfläche enthält, und einen Block mit ausführbarem Code. Der Benutzeroberflächenblock enthält nur lokalisierbare Benutzeroberflächenelemente für die neutrale Kultur, z.B. Zeichenfolgen, Fehlermeldungen, Dialogfelder, Menüs und eingebettete Objektressourcen. Der Codeblock enthält nur den Anwendungscode, der von allen unterstützten Kulturen verwendet werden soll. Die Common Language Runtime unterstützt ein Satellitenassembly-Ressourcenmodell, das ausführbaren Code einer Anwendung von den jeweiligen Ressourcen trennt. Weitere Informationen zur Implementierung dieses Modells finden Sie unter Ressourcen in .NET-Apps.

Fügen Sie für jede lokalisierte Version Ihrer Anwendung eine neue Satellitenassembly hinzu, die den lokalisierten Benutzeroberflächenblock enthält, der in der entsprechenden Sprache für die Zielkultur übersetzt wurde. Der Codeblock aller Kulturen sollte identisch sein. Durch die Kombination einer lokalisierten Version des Benutzeroberflächenblocks mit dem Codeblock erzeugen Sie eine lokalisierte Version Ihrer Anwendung.

In diesem Artikel erfahren Sie, wie Sie die IStringLocalizer<T>- und IStringLocalizerFactory-Implementierungen verwenden. Der ganze Beispielquellencode in diesem Artikel basiert auf den NuGet-Paketen Microsoft.Extensions.Localization und Microsoft.Extensions.Hosting. Weitere Informationen zum Hosting finden Sie unter Generischer .NET-Host.

Ressourcendateien

Der primäre Mechanismus zum Isolieren von lokalisierbaren Zeichenfolgen geschieht über Ressourcendateien. Eine Ressourcendatei ist eine XML-Datei mit der Dateierweiterung RESX. Ressourcendateien werden vor der Ausführung der nutzenden Anwendung übersetzt: sie stellen also übersetzte ruhende Inhalte dar. Ein Ressourcendateiname enthält in der Regel einen Gebietsschemabezeichner und weist die folgende Form auf:

<FullTypeName><.Locale>.resx

Hierbei gilt:

  • <FullTypeName> stellt lokalisierbare Ressourcen für einen bestimmten Typ dar.
  • Das optionale <.Locale>-Element stellt das Gebietsschema des Inhalts der Ressourcendatei dar.

Angeben von Gebietsschemas

Das Gebietsschema sollte die Sprache als absolutes Minimum festlegen, kann aber auch die Kultur (regionale Sprache) und sogar das Land oder die Region definieren. Diese Segmente werden in der Regel durch das Zeichen - getrennt. Mit der zusätzlichen Angabe einer Kultur werden die „Kulturfallbackregeln“ dort angewendet, wo die besten Übereinstimmungen priorisiert werden. Das Gebietsschema sollte einem bekannten Sprachtag zugeordnet werden. Weitere Informationen finden Sie unter CultureInfo.Name.

Kulturfallbackszenarien

Stellen Sie sich vor, Ihre lokalisierte Anwendung unterstützt verschiedene serbische Gebietsschemas und hat die folgenden Ressourcendateien für ihren MessageService:

Datei Regionale Sprache Landesvorwahl
MessageService.sr-Cyrl-RS.resx (Kyrillisch, Serbien) RS
MessageService.sr-Cyrl.resx Kyrillisch
MessageService.sr-Latn-BA.resx (Lateinisch, Bosnien und Herzegowina) BA
MessageService.sr-Latn-ME.resx (Lateinisch, Montenegro) ME
MessageService.sr-Latn-RS.resx (Lateinisch, Serbien) RS
MessageService.sr-Latn.resx Lateinisch
MessageService.sr.resx Lateinisch
MessageService.resx

Die Standardregionalsprache für die Sprache.

Wenn Ihre Anwendung mit CultureInfo.CurrentCulture auf eine Kultur von "sr-Cyrl-RS" festgelegt ausgeführt wird, versucht die Lokalisierung, die Dateien in der folgenden Reihenfolge aufzulösen:

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

Wenn Ihre Anwendung jedoch mit CultureInfo.CurrentCulture auf eine Kultur von "sr-Latn-BA" festgelegt ausgeführt wird, versucht die Lokalisierung, die Dateien in der folgenden Reihenfolge aufzulösen:

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

Die Kulturfallbackregel ignoriert Gebietsschemas, wenn keine entsprechenden Übereinstimmungen vorhanden sind. Dies bedeutet, dass Ressourcendatei Nummer vier ausgewählt wird, wenn keine Übereinstimmung gefunden wird. Wenn die Kultur auf "fr-FR" festgelegt wäre, würde die Lokalisierung mit der Datei MessageService.resx enden, was problematisch sein kann. Weitere Informationen finden Sie unter Ressourcenfallbackprozess.

Ressourcensuche

Ressourcendateien werden automatisch als Teil einer Suchroutine aufgelöst. Wenn sich der Name der Projektdatei vom Stammnamespace Ihres Projekts unterscheidet, kann sich der Assemblyname unterscheiden. Dies kann verhindern, dass die Ressourcensuche andernfalls erfolgreich ist. Um diesen Konflikt zu beheben, verwenden Sie RootNamespaceAttribute, um einen Hinweis für die Lokalisierungsdienste bereitzustellen. Wenn dieser bereitgestellt wird, wird er während der Ressourcensuche verwendet.

Das Beispielprojekt heißt example.csproj, wodurch die Dateien example.dll und example.exe erstellt werden. Es wird jedoch der Localization.Example-Namespace verwendet. Wenden Sie ein Attribut auf assembly-Ebene an, um diesen Konflikt zu beheben:

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

Registrieren von Lokalisierungsdiensten

Um Lokalisierungsdienste zu registrieren, rufen Sie während der Konfiguration der Dienste eine der AddLocalization-Erweiterungsmethoden auf. Dadurch wird Abhängigkeitsinjektion (Dependency Injection, DI) der folgenden Typen aktiviert:

Konfigurieren von Lokalisierungsoptionen

Die AddLocalization(IServiceCollection, Action<LocalizationOptions>)-Überladung akzeptiert einen setupAction-Parameter vom Typ Action<LocalizationOptions>. Dadurch können Sie Lokalisierungsoptionen konfigurieren.

HostApplicationBuilder builder = Host.CreateApplicationBuilder(args);

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

// Omitted for brevity.

Ressourcendateien können überall in einem Projekt gespeichert werden, es gibt jedoch gängige Methoden, die sich als erfolgreich erwiesen haben. Häufig wird der Pfad des geringsten Widerstands befolgt. Für den C#-Code oben gilt:

Dies würde dazu führen, dass die Lokalisierungsdienste im Verzeichnis Resources nach Ressourcendateien suchen.

Verwenden von IStringLocalizer<T> und IStringLocalizerFactory

Nachdem Sie die Lokalisierungsdienste registriert (und optional konfiguriert) haben, können Sie die folgenden Typen mit DI verwenden:

Um einen Nachrichtendienst zu erstellen, der lokalisierte Zeichenfolgen zurückgeben kann, berücksichtigen Sie folgenden 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;
    }
}

Im oben stehenden C#-Code ist Folgendes passiert:

  • Ein IStringLocalizer<MessageService> localizer-Feld wird deklariert.
  • Der primäre Konstruktor definiert einen IStringLocalizer<MessageService>-Parameter und erfasst ihn als localizer-Argument.
  • Die GetGreetingMessage-Methode ruft IStringLocalizer.Item[String] auf und übergibt "GreetingMessage" als Argument.

IStringLocalizer unterstützt auch parametrisierte Zeichenfolgenressourcen. Beachten Sie folgenden 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;
    }
}

Im oben stehenden C#-Code ist Folgendes passiert:

  • Ein IStringLocalizer _localizer-Feld wird deklariert.
  • Der Konstruktor nimmt einen IStringLocalizerFactory-Parameter an, der zum Erstellen von IStringLocalizer aus dem ParameterizedMessageService-Typ verwendet wird, und weist ihn dem _localizer-Feld zu.
  • Die GetFormattedMessage-Methode ruft IStringLocalizer.Item[String, Object[]] auf, übergibt "DinnerPriceFormat", ein dateTime-Objekt und dinnerPrice als Argumente.

Wichtig

IStringLocalizerFactory ist nicht erforderlich. Stattdessen wird bevorzugt, IStringLocalizer<T> zu erfordern, um Dienste zu nutzen.

Beide IStringLocalizer.Item[]-Indexer geben LocalizedString zurück, wobei implizite Konvertierungen in string? vorliegen.

Korrektes Zusammenfügen

Als Beispiel für eine Anwendung, die beide Nachrichtendienste zusammen mit Lokalisierungs- und Ressourcendateien verwendet, betrachten wir die folgende Datei 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();

Im oben stehenden C#-Code ist Folgendes passiert:

  • RootNamespaceAttribute legt "Localization.Example" als Stammnamespace fest.
  • Console.OutputEncoding wird Encoding.Unicode zugewiesen.
  • Wenn ein einzelnes Argument an args übergeben wird, wird CultureInfo.CurrentCulture und CultureInfo.CurrentUICulture das Ergebnis von CultureInfo.GetCultureInfo(String) in Abhängigkeit von arg[0] zugewiesen.
  • Host wird mit Standardwerten erstellt.
  • Die Lokalisierungsdienste (MessageService) und ParameterizedMessageService werden für IServiceCollection für DI registriert.
  • Um Rauschen zu entfernen, wird die Protokollierung so konfiguriert, dass alle Protokollebenen ignoriert werden, die niedriger als eine Warnung sind.
  • MessageService wird aus der IServiceProvider-Instanz aufgelöst, und die sich ergebende Meldung wird protokolliert.
  • ParameterizedMessageService wird aus der IServiceProvider-Instanz aufgelöst, und die sich ergebende formatierte Meldung wird protokolliert.

Jede der *MessageService-Klassen definiert einen Satz von RESX-Dateien mit jeweils einem einzelnen Eintrag. Hier ist der Beispielinhalt für die MessageService-Ressourcendateien, beginnend mit 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>

Hier ist der Beispielinhalt für die ParameterizedMessageService-Ressourcendateien, beginnend mit 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>

Tipp

Alle XML-Kommentare, das Schema und die <resheader>-Elemente der Ressourcendatei werden aus Gründen der Kürze absichtlich weggelassen.

Beispielausführungen

In den folgenden Beispielausführungen werden die verschiedenen lokalisierten Ausgaben bei bestimmten Zielgebietsschemas gezeigt.

Betrachten Sie "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 ¤.

Wenn Sie ein Argument für die .NET CLI zur Ausführung des Projekts weglassen, wird die Standardsystemkultur verwendet (in diesem Fall "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.

Beim Übergeben von "sr-Cryl-RS" werden die richtigen entsprechenden Ressourcendateien gefunden, und die Lokalisierung wird angewendet:

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

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

Die Beispielanwendung stellt keine Ressourcendateien für "fr-CA" zur Verfügung, aber wenn sie mit dieser Kultur aufgerufen wird, werden die nicht lokalisierten Ressourcendateien verwendet.

Warnung

Da die Kultur gefunden wird, die richtigen Ressourcendateien aber nicht, erhalten Sie bei Anwendung der Formatierung eine partielle Lokalisierung:

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 $.

Weitere Informationen