Best Practice Localization: Lokalisierung einer ASP.NET MVC WebApp
Da es häufiger vorkommt, dass eine Website sowohl auf englisch als auch auf deutsch verfügbar sein muss (und mehr!), wollte ich mit diesen Blogpost die wichtigsten Best-Practices versuchen wiedergeben. Ich hatte bereits zu dem Thema einen älteren Blogpost geschrieben, dieser ist aber nicht mehr ganz aktuell.
Was kann man alles mit “Standardmitteln” lokalisieren?
Das augenscheinlichste sind natürlich einfache Texte, welche über Resource Files (ich gehe mal davon aus, dass der Umgang damit bekannt ist. Ansonsten nach .resx suchen) ausgelesen werden. Ein weiteres großes Kapitel ist die Ausgabe von Beschreibungstexten für das Model bzw. Validierungsfehler.
Sample App
Angehangen an den Blogpost ist auch eine Sample App, welche allerdings noch keine Resourcen für Modelbeschreibungen bzw. für die Validierung nutzt – allerdings ist das schon mal ein guter Ausgangspunkt.
Die Gelb markierten Sachen sind “besonders wichtig”. Der andere Source Code kommt entweder mit dem Template mit oder er hat sich in größeren Projekten als ganz nützlich herausgestellt – auch wenn es etwas nach Raketenwissenschaft aussieht.
Wichtigster Teil: Der CurrentLanguageStore. Diese Komponente kann ich aufrufen um herauszufinden welche Sprache der User hat (diese Information schickt der Browser z.B. mit) oder ob der User explizit eine andere Sprache gewählt hat – dann kommt diese in meinem Beispiel aus dem Cookie.
Eine WebApp kann nicht alle Sprachen unterstützen, daher muss irgendwo festgelegt werden, welche Sprachen definitiv unterstützt werden. Dies habe ich über ein Enum namens “LanguageKey” realisiert.
CurrentLanguageStore
Der CurrentLanguageStore besitzt zwei Methoden:
- GetPreferredLanguage: Gib die bevorzugte Sprache des Nutzers zurück.
- SetPreferredLanguage: Setze die bevorzugte Sprache explizit.
Der Code ist etwas größer, weil er aus einem anderen Projekt ist und ich dort via Dependency Injection die benötigten Komponenten mit reinlade. Die “GetPreferredLanguage” Methode sucht nach einem Cookie und ob in diesem Cookie eine valide Sprache abgespeichert ist. Das Cookie kommt nur zum Tragen wenn ein User explizit (z.B. in einem Internet-Cafe) eine andere Sprache haben möchte als der Browser. Dieses Setzen erfolgt bei der “SetPreferredLanguage” Methode. Da ich an der Stelle viel mit Cookies arbeite, habe ich mir einen kleinen Helper gebaut. Den braucht man allerdings nicht unbedingt – man kann diese Speicherung auch pur vornehmen.
public class CurrentLanguageStore
{
/// <summary>
/// Cookie Repository for getting and setting cookie values.
/// </summary>
private ICookieRepository _cookieRep;
/// <summary>
/// HttpContext for accessing the HttpRequests UserLanguages
/// </summary>
private HttpContextBase _context;
public CurrentLanguageStore()
{
this._cookieRep = new HttpCookieRepository();
this._context = new HttpContextWrapper(HttpContext.Current);
}
/// <summary>
/// Default ctor for a new instance.
/// </summary>
/// <param name="baseContext">HttpBaseContext for accessing the HttpRequest.</param>
/// <param name="cookieRepository">Implementation of ICookieRepository.</param>
public CurrentLanguageStore(HttpContextBase baseContext, ICookieRepository cookieRepository)
{
_cookieRep = cookieRepository;
_context = baseContext;
}
/// <summary>
/// Gets the preferred language.
/// </summary>
/// <returns></returns>
public LanguageKey GetPreferredLanguage()
{
string[] browserLanguages = this._context.Request.UserLanguages;
if (this._cookieRep.HasElement(CookieKey.UserLanguage))
{
string cookieResult = this._cookieRep.GetElement(CookieKey.UserLanguage);
if (string.IsNullOrWhiteSpace(cookieResult))
return LanguageKey.En;
if (cookieResult.ToLower() == LanguageKey.De.ToString().ToLower())
return LanguageKey.De;
return LanguageKey.En;
}
if (browserLanguages == null)
return LanguageKey.En;
foreach (var language in browserLanguages)
{
if (language.StartsWith(LanguageKey.De.ToString().ToLower()))
return LanguageKey.De;
else if (language.StartsWith(LanguageKey.En.ToString().ToLower()))
return LanguageKey.En;
}
return LanguageKey.En;
}
public void SetPreferredLanguage(LanguageKey key)
{
if (this._cookieRep.HasElement(CookieKey.UserLanguage))
{
this._cookieRep.UpdateElement(CookieKey.UserLanguage, key.ToString());
}
else
{
this._cookieRep.AddElement(CookieKey.UserLanguage, key.ToString());
}
}
}
Meine Anwendung unterstützt nur Deutsch und Englisch – auch der regional Code z.B. für de-AT ist mir an der Stelle egal, wäre aber auch möglich.
public enum LanguageKey
{
De = 1031,
En = 1033
}
Das ist im Grunde die Infrastruktur (+ die Hilfsklassen für den Zugriff auf das Cookie) um die Lokalisierung zu machen.
Der LanguageController
Der LanguageController hat nur die Aufgabe dem Benutzer ein UI-Element anzuzeigen um die Sprache zu wechseln und den Aufruf auch an den CurrentLanguageStore weiterzugeben.
public class LanguageController : Controller
{
private CurrentLanguageStore _languageStore;
public LanguageController()
{
this._languageStore = new CurrentLanguageStore();
}
public RedirectToRouteResult SwitchLanguage(LanguageKey key)
{
this._languageStore.SetPreferredLanguage(key);
return RedirectToAction("Index", "Home");
}
public ActionResult LanguageBox()
{
LanguageKey languageNow = this._languageStore.GetPreferredLanguage();
if (languageNow == LanguageKey.De) ViewBag.AvailableLanguage = LanguageKey.En;
else ViewBag.AvailableLanguage = LanguageKey.De;
return View();
}
Der View zur LanguageBox:
Der Code hier ist nicht besonders schön (und geht schon schief wenn ich nur eine weitere Sprache unterstützen möchte), aber an der Stelle gibt es für euch noch Potenzial zum Verbessern ![]()
@using MvcLocalization.WebApp.Infrastructure
@{
Layout = "";
var availableLanguageText = "";
var availableLanguage = LanguageKey.De;
if(ViewBag.AvailableLanguage == LanguageKey.De)
{
availableLanguageText = "Deutsch";
availableLanguage = LanguageKey.De;
}
else
{
availableLanguageText = "English";
availableLanguage = LanguageKey.En;
}
}
@Html.ActionLink(availableLanguageText, "SwitchLanguage", "Language", new { key = availableLanguage.ToString() }, null)
Ganz wichtig: Anwendung des CurrentLanguageStore über einen BaseController
In dem alten Blogpost habe ich geschrieben, dass man nun den CurrentLanguageStore in einem ActionFilter einsetzen kann und dort die CurrentCulture des Threads zu manipulieren. Allerdings ist dieser Weg nicht richtig!
Grund: Das Modelbinding + die Validierung wird ausgeführt bevor die ActionFilter zum Tragen kommen, daher muss die Lokalisierung vorher passieren!
Abhilfe schafft das Überschreiben der ExecuteCore Methode in einem Basis-Controller.
public class BaseController : Controller
{
protected override void ExecuteCore()
{
CurrentLanguageStore store = new CurrentLanguageStore();
LanguageKey key = store.GetPreferredLanguage();
CultureInfo language = new CultureInfo(key.ToString());
Thread.CurrentThread.CurrentCulture = language;
Thread.CurrentThread.CurrentUICulture = language;
base.ExecuteCore();
}
}
Die Anwendung
Damit sollte ich nun in der Lage sein, einfach über Resourcen Dateien meine Texte sowohl im Model als auch im View zu lokalisieren. Eine größere Beschreibung zum Thema findet sich auch auf diesem Blog – von der Herangehensweise ist es ähnlich wie meine Variante. Allerdings dort bereits mit Model-Lokalisierung ausgestattet – ein Blick lohnt!
<p>
@TestResource.Title
</p>







Sebastian
3. August 2011
Hallo Robert,
ich hatte letztens auch mit dem Thema zu tun und fand den Artikel sehr hilfreich.
http://adamyan.blogspot.com/2010/02/aspnet-mvc-2-localization-complete.html
Der verlinkte Ansatz arbeitet sehr ähnlich und nutzt die preferred Language aus dem HTTP-Header und das Setzen der Sprache erfolgt hier in der Global.asax. Des Weiteren fand ich die Aufteilung der Ressourcen nach View sehr gut gelungen.
Hat die von dir beschriebene Vorgehensweise (über den Cookie) Vorteile gegenüber der Auswertung des HTTP-Headers, oder ist es gar besser beide zu nutzen?
Bruß, Sebastian
Robert Mühsig
3. August 2011
Ich les die Sprache auch aus den Http-Headern. Im Request schickt der Browser ja die UserLanguage mit. Diese lese ich auch aus und schau ob ich eine dieser Sprachen anbieten kann. Der Cookie kommt nur zum Tragen wenn der User explizit eine andere Sprache wählt – hier ist das Szenario z.B. in einem Internet-Cafe. Ich speicher mir das im Cookie, es würde aber theoretisch auch die Session gehen oder gar die URL. Je nachdem was man erreichen will.
Ich hab das setzen der Sprache in einem Base-Controller gemacht, weil ich meine Global.asax möglichst schlank halten wollte. Aber vom Prinzip her funktioniert beides ähnlich
Björn
3. August 2011
Hi Robert,
wie es der Zufall will brauch ich sowas die Tage. Hat mir die Suche schon mal erleichtert
Besten Dank dafür!
Gruß
Björn
squadwuschel
5. August 2011
Hio,
sehr nice beschrieben, ich hatte z.B. auch so meine Probleme beim Einbinden von Globalen Lokalisierungsdateien, was ich hier mal niedergeschrieben habe, evtl. hilfts wem weiter.
http://squadwuschel.wordpress.com/2011/06/22/asp-net-mvc-3-lokalisierung/
mfg squadwuschel