25. Juli 2016
Marek Böttcher
0

Lokalisierung mit ASP.NET Core

Im Gegensatz zu ASP.NET Core RC1 werden ab RC2 und RTM die Lokalisierungsfeatures besser unterstützt. Es gibt bei ASP.NET Core unterschiedliche Möglichkeiten wie man die Lokalisierung (multilanguage support) einer Website hinzuzufügen kann. Ich möchte hier eine exemplarische Vorgehensweise zeigen, so wie sie in einem Projekt der Saxonia Systems AG zum Einsatz kam. Wir haben uns bei diesem Projekt für die Verwendung von Resource-Dateien (.resx) entschieden, weil damit das Lokalisierungsthema aus dem Quellcode ausgelagert wird. So kann man z.B. die einzelnen Dateien einem externen Übersetzer zum Bearbeiten geben, ohne das dafür Kenntnisse über den Quellcode benötigt werden.

Exemplarische Vorgehensweise

Services für Lokalisierung hinzufügen:

Zunächst werden die für die Lokalisierung benötigten Services der startup.cs hinzugefügt:

using Microsoft.AspNetCore.Mvc.Razor;
  
public void ConfigureServices(IServiceCollection services)
{
  services.AddLocalization(options => options.ResourcesPath = "Resources");
  services.AddMvc()
  .AddViewLocalization(LanguageViewLocationExpanderFormat.Suffix)
  .AddDataAnnotationsLocalization();

Der Resources Ordner wird im Wurzelverzeichnis der Projektstruktur angelegt. Dieser Ordner enthält später die resx-Dateien mit den Übersetzungen:

resources-folder@saxonia-systems

Supported Cultures hinzufügen:

Ebenfalls in der startup.cs werden die unterstützten Landessprachen festgelegt.

using Microsoft.AspNetCore.Localization;

public void Configure(IApplicationBuilder app, IHostingEnvironment env, ILoggerFactory loggerFactory)
{
  var supportedCultures = new[]
  {
    new CultureInfo("de-DE"),
    new CultureInfo("fr-FR")               
  };

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

IViewLocalizer Service im View verfügbar machen:

Am Beispiel des Views der About-Page wird gezeigt wie der Localizer Service verwendet wird. Zunächst wird per Injection der IViewLocalizer in der View verfügbar gemacht.

@inject IViewLocalizer Localizer

… und dann mit Razor Syntax mit dem zu übersetzenden Text aufgerufen

@Localizer["Use this area to provide additional information."]
@using Microsoft.AspNetCore.Mvc.Localization
@inject IViewLocalizer Localizer
 
@{
  ViewData["Title"] = "About";
}

Resourcendateien im Projekt anlegen

Die resx-Dateien mit den Übersetzungen werden im Projekt angelegt. Diese werden mit Add New Item als Resources File in Visual Studio hinzugefügt.

create-resources-file@saxonia-systems

Dabei gibt es zu beachten, dass der Dateiname der resx-Datei exakt der Ordnerstruktur des Views entspricht.

file-name@saxonia-systems

Hinweis:

Möchte man weitere Sprachen hinzufügen, bietet es sich an eine resx-Datei zu kopieren und für die jeweilige Sprache umzubenennen. Wenn man die Datei jedoch mit dem Windows Explorer kopiert, endet der Dateiname auf  erst einmal auf „Copy“ und Visual Studio legt automatisch eine C#-Klassendatei an, die wir jedoch nicht benötigen. Wenn man diese Dateien daraufhin löscht, kann es im Visual Studio zu Inkonsistenzen bei den Pending Changes kommen. Derartige Probleme treten nicht auf, wenn man die resx-Dateien direkt in Visual Studio kopiert und umbenennt.

Gemäß der Einstellung in unserem Localization Service .AddViewLocalization(LanguageViewLocationExpanderFormat.Suffix) werden die supported cultures anhand der Dateiendung gefunden. Ein gültiger Dateiname für die deutsche Sprachlokalisierung sieht demnach wie folgt aus:

Views.Home.About.de-DE.resx

Der pinke Teil des Dateinamens muss exakt der Ordnerstruktur des Projekts entsprechen. Demnach liegt die View About.cshtml im folgenden Verzeichnis:

about-cshtml@saxonia-systems

Resourcendateien mit Inhalt füllen

Die folgende Abbildung zeigt die deutsche resx-Datei für die View „About“

resx-file@saxonia-systems

Auf der linken Seite steht der Key (Name) nachdem auf der Page gesucht wird und auf der rechten Seite (Value) der übersetzte Text. Wir haben uns entschieden für unser Projekt die englische Sprache als Default-Key zu benutzen, so dass die Page auf englisch angezeigt wird, sollte eine bestimmte Sprache auf dem System nicht vorhanden sein. Es empfiehlt sich beim Key die Satzzeichen (in diesem Fall der Punkt) mit anzugeben, da es hier bei der Lokalisierung auch zu Unterschieden kommen kann, z.B. bei der spanischen Sprache das umgekehrte Fragezeichen.

Der Resource Key wird jetzt in der View „About.cshtml“ wie folgt verwendet:

@inject IViewLocalizer Localizer

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

Startet man jetzt die Beispielanwendung erscheint die folgende Seite, wenn man auf „About“ klickt:

german-about@saxonia-systems

Obwohl die Default culture in der startup.cs Datei momentan auf englisch eingestellt ist,

DefaultRequestCulture = new RequestCulture(„en-US“)

wird die Webseite jetzt schon ins Deutsche übersetzt, weil die Request Culture gemäß der Prioritätsfolge in den Windows Internetoptionen ausgesucht wird. Die folgende Abbildung zeigt, wie man die Prioritätsfolge ändern kann:

internet-explorer-settings@saxonia-systems

language-priority@saxonia-systems

Nachdem beispielsweise die Sprache französisch nach oben verschoben wurde, meldet sich die „About“ Page sofort nach dem Start auf französisch:

france-about@saxonia-systems

Ob das Umschalten der verschiedenen Sprachen funktioniert kann man im Browser testen, indem man die Adresse der aktuellen Webseite um folgende Erweiterung ergänzt:

Für die Umschaltung auf deutsch: ?culture=de-DE

Für die Umschaltung auf französisch: ?culture=fr-FR

language-switch@saxonia-systems

Den Fallback auf die englische Key (Name) Sprache der resx-Datei zu testen ist gar nicht so einfach. Versucht man z.B. einen Webrequest für eine Sprache für die keine Resource hinterlegt ist, z.B. ?culture=es-CL, dann wird die Seite nicht etwa in der Fallback Sprache auf englisch angezeigt, sondern gemäß der Reihenfolge in den Windows Interneteinstellungen auf deutsch.

Möchte man ein Sprachumschaltung zu englisch haben, sollte man sich nicht auf den Fallback zur Keysprache englisch verlassen. Zuverlässiger ist es sich dafür eine eigene Resource z.B. „… en-US.resx“ anzulegen, auch wenn dann Key (Name) und Value der Resx-Datei identisch sind.

resx-datei@saxonia-systems

Persistentes Speichern der Spracheinstellungen mit Cookies

Bei produktiven Webanwendungen ist es üblich, dass die vom Anwender festgelegten Spracheinstellungen dauerhaft auf dem Client gespeichert werden. Das passiert meist in Form von Cookies. Im Folgenden wird exemplarisch gezeigt, wie dieser Mechanismus in einem ASP.NET Core Projekt funktioniert. Die Sprachumschaltung ist in diesem Beispiel über Buttons mit Flaggensymbolen realisiert:

multi-language@saxonia-systems

Der dazugehörige Sourcecode des Views:

@using Microsoft.AspNetCore.Mvc.Localization
@inject IViewLocalizer Localizer

<div class="row text-right">
  <b>@Localizer["Change language:"]&emsp;</b>      
  <a asp-controller="Home" asp-action="SetLanguage" asp-route-culture="en-GB" asp-route-returnUrl="@Context.Request.Path"><img src="~/images/flags/png/48/United-kingdom.png" /></a>
  <a asp-controller="Home" asp-action="SetLanguage" asp-route-culture="de-DE" asp-route-returnUrl="@Context.Request.Path"><img src="~/images/flags/png/48/Germany.png" /></a>
  <a asp-controller="Home" asp-action="SetLanguage" asp-route-culture="hu-HU" asp-route-returnUrl="@Context.Request.Path"><img src="~/images/flags/png/48/Hungary.png" /></a>     
</div>

In diesem Beispiel wird der IViewLocalizer Service für die Beschreibung der Buttons verwendet, damit der Anwender über die Funktionsweise der Buttons in seiner Landessprache aufgeklärt wird. Beim jeweiligen Klick auf das Flaggensymbol wird die SetLanguage Methode im Home-Controller aufgerufen. Als Parameter wird der jeweilige Countrycode übergeben. Die Aufgabe der SetLanguage Methode besteht darin, auf dem Client einen Cookie mit dem übergebenen Countrycode zu speichern. Die abschließende LocalRedirect Methode bewirkt ein sofortiges Neuaufrufen der aktuellen Seite. Der gerade gespeicherte Cookie wird vom Webserver gelesen und die Seite erscheint in der im Cookie gespeicherten Landessprache.

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);
}

Beispiel für einen auf dem Client gespeicherten Cookie mit deutscher Landessprache:

.AspNetCore.Culture
c%3Dde-DE%7Cuic%3Dde-DE
localhost/
1024
1536249344
30602411
3810627421
30528985
*

Lokalisierung im ViewModel

Im bisher gezeigten Beispiel wurde der Localizer-Service nur im View verwendet. Die Properties im ViewModel kann man wie folgt lokalisieren:

using Microsoft.AspNetCore.Localization;

[Display(Name = "Workshift", ResourceType = typeof(ViewModels_DriverProfile_DriverProfileViewModel))]
public DriverProperties.Workshift Workshift { get; set; }

Die dazugehörige .resx-Datei befindet sich im Ordner Resources und hat den Namen ViewModels.DriverProfile.DriverProfileViewModel.de-DE.resx. Bei ResourceType wird der Pfad zum ViewModel mit Unterstrichen als Trennung angegeben.

Lokalisierung im Controller

Es gibt Anwendungsfälle wo man den Lokalisierung-Service im Controller verwenden möchte, z.B. um enums zu lokalisieren. Hierfür nutzen wir den IStringLocalizer und machen ihn im Controller verfügbar:

using Microsoft.Extensions.Localization;

  public class DriverProfileController : Controller
  {
    private readonly IStringLocalizer<DriverProfileController> _localizer;

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

Im folgenden Beispiel werden die Values des Enums mit dem IStringLocalizer übersetzt.

private SelectList GetSelectListByEnum<T>()
{
  var selectListItems = Enum.GetValues(typeof(T)).Cast<T>()
                          .ToDictionary(key => key, value => _localizer[value.ToString()]);
  var result = new SelectList(selectListItems, "Key", "Value");
  return result;
}

Damit das funktioniert benötigen wir eine resx-Datei mit dem Namen „Controllers.DriverProfileController.de-DE.resx“, welche sich im Resources Verzeichnis befindet.

Fazit

Wie das vorgestellte Beispiel gezeigt hat, lassen sich Lokalisierungsmechanismen in ASP.NET Core ab Version RC2 relativ leicht implementieren. Es gibt aber noch weitere praktikable Möglichkeiten der Lokalisierung die in diesem Beispiel nicht erwähnt wurden, z.B. kann der IStringLocalizer Service, anstelle mit resx-Dateien auch mir der ResourceManager-Klasse arbeiten.

Marek Böttcher ist Senior Consultant für Softwareentwicklung im Microsoft-Umfeld bei der Saxonia Systems AG. Neben der Client Entwicklung liegt sein Schwerpunkt auf der Entwicklung moderner Webanwendungen mit ASP.NET Core.

Xing 

TeilenTweet about this on TwitterShare on Facebook0Share on Google+0Share on LinkedIn0