Glengamoi (Forum) · AspHeute · .NET Heute (RSS-Suche) · AspxFiles (Wiki) · .NET Blogs

Mehrsprachige Applikationen in .Net

Geschrieben von: Dani Meier
Kategorie: .NET Allgemein

This printed page brought to you by AlphaSierraPapa

Sicher sind die meisten schon einmal vor der Aufgabe gestanden, eine Applikation mehrsprachig umzusetzen. Mögliche Lösungsansätze gibt es dafür mehr als genug:

Datenbanken haben den Nachteil, dass man seine Strings nicht mal einfach so dem Übersetzer geben kann. Und Flat-Files sind wie der Name schon sagt, einfach zu flach - da mit normalem Aufwand keine strukturierte Ablage (und vor allem Abfrage) der Strings möglich ist, verliert man schnell den Überblick. Ressource-Files (.resx) liegen sehr nahe, da der Zugriff auf diese bereits vom Framework bereitgestellt wird, und da Ressource-Files XML-Files sind, ist auch der Umgang mit ihnen kein Problem; es gibt aber durchaus Situationen, wo "normale" XML-Files besser am Platz wären - z.B. wenn Plattform-Unabhängigkeit eine Rolle spielt. Wie eine Lösung damit aussehen könnte, wird in diesem Artikel beschrieben.

Die Idee

Die Anforderungen sind folgende:

Die Lösung

Wenn Ressource-Files wegfallen, liegen "normale" XML-Files nahe. Alle Strings werden in ein zentrales XML-File geladen. Das XML-File wird eingelesen und entsprechend gecached - mittels einer CacheDependency auf dem XML-File wird sichergestellt, dass bei jeder Aktualisierung des Files der Cache ebenfalls erneuert wird - so läuft man auch bei sehr vielen Strings nicht Gefahr, Performance-Probleme zu erhalten. Der Zugriff auf die Strings funktioniert über eine zentrale Klasse, die die ganze Zugriffs- und Caching-Logik kapseln soll.

Datenmodell

Die XML-Datenstruktur sieht exemplarisch wie folgt aus:

<StringPool>
<Region name="Administration">
    <Text key="CommitButton">
      <D>Bestätigen</D>
      <F>Confirmer</F>
    </Text >
    <Text key="DeleteButton">
      <D>Löschen</D>
      <F>Supprimer</F>
    </ Text>
</Region>
<Region name="Gallery">
    <Text key="PictureInit">
      <D>Bild</D>
      <F>Image</F>
    </Text>
</Region>
</StringPool>

Die Unterteilung in die Region-Nodes ermöglicht es, eine gewisse Struktur in das XML-File zu bringen - so behält man auch bei hunderten von Texten den Überblick. Die Sprachen können frei ergänzt werden - die Bezeichnung des Sprach-Nodes (hier D, F) ist dabei egal, da dessen Bezeichnung dann einfach als Parameter mitgegeben wird (dazu später mehr). Und da es sich hierbei augenscheinlich um ein ganz normales XML-File handelt, kann dieses natürlich auch einfach mit Notepad bearbeitet werden.

Als Datenspeicher für die Daten bieten sich durch das Key / Value - Verfahren Dictionary-Objekte an. Jede Hierarchie-Stufe (Region, Text, [Sprache]) wird in ein eigenes Dictionary-Objekt gespeichert - und diese werden dann entsprechend verschachtelt.

Für die beiden Stufen Regionen und Text werden HashTables verwendet - für die Texte ein StringDictionary; da auf dieser Ebene nur Strings abgelegt werden, bietet sich dieses durch seine verbesserte Performance gegenüber einer HashTable an.

Das Datenmodell der StringPool-Klasse:

Real-Life

So weit die Theorie - aber wie programmiert man dies nun in C#?

Die Klasse StringPool - die das ganze heavy lifting für uns machen wird - hat folgende Signatur:

public class StringPool
{
  public void LoadStrings()

  private XmlDocument LoadSourceFile(string file)

  public string GetString(string region, string key, string language)

  public StringPool GetInstance()
}

Vier einfache Methoden reichen also bereits:

Single-Leben

Nun wäre es natürlich nicht sehr klug, bei jedem String-Request das ganze XML-File in die Datenstruktur zu laden, nur um danach einen einzigen String zu verwenden und dann das ganze Objekt wieder zu "zerstampfen"…

Ziel muss es also sein, die StringPool-Instanz anfangs einmal zu erstellen, und dann für alle User über ein geschicktes Caching-Verfahren möglichst performant verfügbar zu machen - wobei es natürlich keinen Sinn macht, für jeden User ein eigenes StringPool-Objekt zu führen.

Mit einem Singleton-Pattern können wir genau das sicherstellen - nämlich dass zu jedem Zeitpunkt immer nur genau ein StringPool-Objekt besteht - egal wieviele User die Applikation gerade nutzen. Diese eine Instanz wird dann gecached, und jeweils neu erstellt, wenn Änderungen am XML-File gemacht wurden.

Für WebApplikationen bietet sich hier natürlich das Cache[]-Objekt an - aber was ist mit WinForms? Das Cache[]-Objekt befindet sich im Namespace System.Web.Caching - hört sich also nicht passend für eine WinForm-Anwendung an - ist es aber absolut! Über die Klasse System.Web.HttpRuntime kann man für WinForm- (oder natürlich auch andere) Applikationen ein Cache[]-Objekt erstellen.

if(HttpContext.Current == null)
  myCache = HttpRuntime.Cache;
else
  myCache = HttpContext.Current.Cache;

In ersterem Fall (betrifft alle nicht ASP.NET-Anwendungen) beziehen wir unser Cache-Objekt also über die HttpRuntime-Klasse, im zweiten (ASP.NET-Anwendungen) über den aktuellen HttpContext.

Jetzt muss die Methode für uns noch prüfen, ob im Cache bereits eine Instanz von StringPool besteht - wenn nicht wird diese erstellt, die Strings geladen und dann in den Cache gespeichert.

if(myCache[CACHE_KEY] == null) {
  // Erstelle StringPool-Instanz und lade Strings
  StringPool sp = new StringPool();
  sp.LoadStrings();
				
  // Cache neu füllen
  myCache.Insert(CACHE_KEY, sp, new CacheDependency(SourceFile));
}

Damit die Klasse nicht anderweitig instanziert werden kann (also unseren Singleton umgehen), definieren wir einen privaten Konstruktor.

Praxis

Nun ist unsere Klasse schon bereit, genutzt zu werden; wir können unsere Strings über die GetString-Methode abfragen - ohne jemals Gefahr zu laufen, dass wir auf ein nicht instanziertes Objekt zugreifen und somit eine NullRefereceException werfen:

String myString = StringPool.GetInstance().GetString("Administration", "CommitButton", "D");

Sollte die übergebene Sprache (hier: "D") in unserer Datenquelle nicht vorhanden sein, wird eine im .config-File definierte Default-Sprache verwendet. Ein Aufruf ohne den Sprachparameter funktioniert dank einer Überladung der GetString()-Methode ebenfalls - auch dort wird dann jeweils die definierte Default-Sprache verwendet.

String myString = StringPool.GetInstance().GetString("Administration", "CommitButton");

Schlußbemerkung

Dieser Artikel soll einen einfachen, aber dennoch praktikablen Ansatz bieten, mehrsprachige Applikationen in .Net zu entwickeln. Vorteile dieses Ansatzes liegen sicherlich in der einfachen Handhabung des XML-Files - es werden keine speziellen Programme oder User-Interfaces benötigt, um die Strings anzupassen - und in der Plattformunabhängigkeit. Das hier verwendete Konzept kann ohne Probleme auch auf andere Datenquellen (z.B. eine Datenbank) angewendet werden - eine Anpassung der LoadStrings()-Methode genügt.

This printed page brought to you by AlphaSierraPapa

Download des Codes

Klicken Sie hier, um den Download zu starten.
http://www.aspheute.com/code/20040922.zip

 

©2000-2006 AspHeute.com
Alle Rechte vorbehalten. Der Inhalt dieser Seiten ist urheberrechtlich geschützt.
Eine Übernahme von Texten (auch nur auszugsweise) oder Graphiken bedarf unserer schriftlichen Zustimmung.