Laden...

[Tutorial] Lokalisierung von Texten in .NET

Erstellt von J2T vor 15 Jahren Letzter Beitrag vor 8 Jahren 39.237 Views
J
J2T Themenstarter:in
66 Beiträge seit 2008
vor 15 Jahren
[Tutorial] Lokalisierung von Texten in .NET

Vielen Dank an das myCSharp.de-Team und die Poweruser für das Feedback und das Korrekturlesen. Dieses Tutorial entstammt ursprünglich aus meinem Blog und wurde auch dort veröffentlicht.

Einleitung

Ein effizientes und zugleich einfaches System zur Lokalisierung, zumindest von Texten, muss in jeder Anwendung, die mehrsprachig ausgeliefert werden soll, vorhanden sein.

Wichtig dabei ist das alle Texte in externen Dateien liegen, damit sie sich problemlos übersetzen lassen. Texte gehören keinesfalls in den Programmcode. Es ist dann auch eminent wichtig das diese Textdateien eine gute Struktur haben, damit sich diese komfortabel übersetzten lassen und in die Anwendung eingebunden werden können. Deshalb sollten alle Texte übersichtlich formatiert und gleich in der richtigen Reihenfolge angeordnet sein. Es kann auch von Vorteil sein an diversen Stellen Zusatzinformationen an den Übersetzer mitzugeben. Wie so eine Datei aussehen kann sehen wir gleich, nur vorweg, es ist eh nicht die optimale Lösung.

Ich möchte euch bevor wir zur Umsetzung mit .NET kommen noch zwei andere Möglichkeiten zeigen wie man Texte lokalisieren kann. Danach wird dann auch deutlich warum die Umsetzung mit .NET das Beste ist.

Die Umsetzung

Alle drei Umsetzungen möchte ich anhand von diesem kleinen Beispiel verdeutlichen:

MenuEntry optionsMenuEntry = new MenuEntry("Options")); 

Generell wird alles mit C# umgesetzt!

Lokalisierung über Stringexternalisierung

Die Möglichkeit ist die einfachste aber zugleich auch die Schlechteste. Zum Einem liegt der Text direkt im Programmcode und zum Anderen muss die Sprache zur Kompilierung feststehen. Für eine kleine Anwendung mit wenig Text ist es aber trotzdem eine schönere Lösung als die Texte direkt zu schreiben.

Prinzipiell beruht das Verfahren auf einer statischen Klasse in der die Strings externalisiert werden.

   1. using System;  
   2. using System.Collections.Generic;  
   3. using System.Text;  
   4.   
   5. namespace MarioWorldWars.Text  
   6. {  
   7.     sealed class GermanText  
   8.     {  
   9.         public static String OPTIONSMENU = "Optionen";  
  10.     }  
  11. }  
MenuEntry optionsMenuEntry = new MenuEntry(GermanText.OPTIONSMENU)); 

Lokalisierung über externe Dateien

Kommen wir zu der Möglichkeit die ich in der Einleitung erwähnt habe. Nun kommen alle Texte sauber in eine externe Datei. Die könnte zum Beispiel so ausehen:

MENU_OPTS                         “Einstellungen”
MENU_CONTROLLEROPTS         “Steuerungseinstellungen” ; bitte auf Plural achten

Hinter dem Semikolon könnte jetzt für den Übersetzter noch eine Bemerkung angebracht werden.

Diese Datei wird jetzt einfach eingelesen und der entsprechende String kann zurückgeben werden. Der Wechsel zwischen den verschiedenen Dateien ist natürlich problemlos zur Laufzeit möglich. Ein kleines Beispiel zum Einlesen:

   1. using System;  
   2. using System.Collections.Generic;  
   3. using System.Text;  
   4. using System.IO;  
   5.   
   6. namespace ConsoleApplication1  
   7. {  
   8.     class FileReader  
   9.     {  
  10.   
  11.         private StreamReader sr;  
  12.   
  13.         public FileReader()  
  14.         {  
  15.             //read the file  
  16.             sr = new StreamReader(@"test.txt", System.Text.Encoding.Default);  
  17.   
  18.         }  
  19.   
  20.         public String getString(String patter)  
  21.         {  
  22.             String line = string.Empty;  
  23.             try  
  24.             {  
  25.                 while ((line = sr.ReadLine()) != null)  
  26.                 {  
  27.   
  28.                     if (line.Contains(patter)) //else read next line  
  29.                     {  
  30.                         int first = line.IndexOf('"');  
  31.                         int last = line.LastIndexOf('"');  
  32.                         //return the correct text related to the pattern  
  33.                         return line = line.Substring(++first, last - first);  
  34.                     }  
  35.                     else line = "Not found";  
  36.                 }  
  37.             }  
  38.             catch (Exception e)  
  39.             {  
  40.                 Console.WriteLine("The file could not be read:");  
  41.                 Console.WriteLine(e.Message);  
  42.             }  
  43.   
  44.             return line;  
  45.         }  
  46.     }  
  47. }  

Ist natürlich so nicht zu gebrauchen, da die Funktion fehlt, um die Datei zu wechseln.

   1. FileReader fr = new FileReader();  
   2. MenuEntry optionsMenuEntry = new MenuEntry(fr.getString("MENU_OPTS"));  

Im Übrigen wurde dieses System in der /GameStar/Dev Ausgabe 01/08 vorgestellt in der es auch generell um die Lokalisierung von Spielen ging.

Lokalisierung mit .NET

Kommen wir nun zu der elegantesten Lösung. Die Textlokalisierung mit Ressourcendateien durch .NET!Das System ist sehr einfach und elegant. Auch wenn diese Dateien ja eigentlich zum Projekt gehören, lassen sie sich extern öffnen und so von Lokalisierungsagenturen bearbeiten. Somit gehören sie nicht direkt zum Programmcode und widersprechen deshalb auch nicht der wichtigen Regel zur Lokalisierung.

Hierzu legt man einfach für jede Sprache eine entsprechende Ressourcendatei an, in der dann die verschiedenen Texte in der jeweiligen Sprache verfasst werden. Auch da ist es direkt möglich ein entsprechenden Kommentar hinzuzufügen. Die Datei könnte dann wie folgt aussehen:

Der Name bleibt logischerweise in jeder Ressourcendatei der gleiche lediglich das Value ändert sich. Wichtig ist die Benennung der Datei. Das heißt das .NET Framework erkennt anhand des Suffixes um welche Sprachressource es sich handelt.

Das heißt beispielsweise das die Ressouce für den deutschen Text den Suffix *.de-DE am Ende des Dateinamens tragen muss. Es ist auch möglich eine Datei ohne Suffix anzulegen. Dann wird diese ausgewählt wenn ein Suffix, der übergeben wird, nicht explizit definiert wurde.

Alle Ressourcendateien müssen im Übrigen vor dem Suffix den gleichen Namen haben, damit diese von dem ResourceManager erkannt werden. Hier in dem Beispiel ResStrings.


ResStrings.resx
ResStrings.de-DE.resx
ResString.cs-CZ.resx

Alle Suffixe gibt es in der CultureInfo-Klasse nachzulesen.

Nun schreiben wir eine Klasse die zwei statische Methoden enthält um global auf diese zugreifen zu können. Zum Einen eine Methode die die jeweilige Textressource auswählt und zum Anderen eine die uns den entsprechenden String zurückgibt.

   1. using System;  
   2. using System.Collections.Generic;  
   3. using System.Text;  
   4. using System.Globalization;  
   5. using System.Threading;  
   6. using System.Resources;  
   7. using System.Reflection;  
   8.   
   9. namespace Application.Text  
  10. {  
  11.     sealed class Localization  
  12.     {  
  13.   
  14.         private static ResourceManager resMgr;  
  15.          
  16.         public static void UpdateLanguage(string langID)  
  17.         {  
  18.             try  
  19.             {  
  20.                 //Set Language  
  21.                 Thread.CurrentThread.CurrentUICulture = new CultureInfo(langID);  
  22.   
  23.                 // Init ResourceManager  
  24.                 resMgr = new ResourceManager("Application.Text.ResStrings", Assembly.GetExecutingAssembly());  
  25.                  
  26.             }  
  27.             catch (Exception ex)  
  28.             {  
  29.             }  
  30.         }  
  31.   
  32.         public static string getString(String pattern)  
  33.         {  
  34.            return resMgr.GetString(pattern);  
  35.         }  
  36.   
  37.     }  
  38. }   

Nun noch ein Beispiel wie diese Klasse im Zusammenhang mit unserem Beispiel funktioniert:

   1. Localization.UpdateLanguage("de-DE");  
   2.   
   3. MenuEntry optionsMenuEntry = new MenuEntry(Localization.getString("MENU_MAINOPTS"));  

Der Aufruf zumindest einmalige Aufruf der UpdateLangugage() Methode ist erforderlich, weil in ihr der ResourceManager initialisiert wird.

Im Übrigen ist es auch möglich sich die String so zurückgeben zu lassen:

MenuEntry optionsMenuEntry = new MenuEntry(ResStrings.MENU_MAINOPTS);

Man ist also nicht gezwungen sich eine Methode zu schreiben, die den String zurückgibt. Das funktioniert weil hinter der eigentlichen Ressourcendatei eine Klasse steht und man so auf deren statischen Felder zugreifen kann.

Wollen wir die Sprache wechseln genügt der Aufruf der UpdateLangugage() Methode:

   1. Localization.UpdateLanguage("de-DE");  

Würden wir jetzt zum Beispiel den String “en-GB” übergeben, würde die Ressource ohne Suffix aufgerufen werden, da ja “en-GB” von uns nicht explizit angeben wurde.

Ich möchte noch darauf hinweisen, das wenn ihr mit Systemen wie den WinForms arbeitet, nicht vergessen dürft etwaige Buttons, Labels usw. dann auch zu updaten. Dies könnte gleich in UpdateLangugage() erfolgen oder besser in einer separaten Methode die dann von UpdateLangugage() aufgerufen wird.

Meine Quelle für diese Umsetzung war das kompakte Tutorial von Martin H.!

Hilfreiche Tools

Hier möchte ich noch kurz zwei wunderbare Tools erwähnen, die die Arbeit mit den Ressourcendateien erheblich vereinfachen.

Es kann ganz schön nervig sein bei mehreren Ressourcen und viel Text immer zwischen den Reitern hin- und herzuschalten. Abhilfe schafft hier der Zeta Resource Editor von Uwe Keim. Mit diesem Tool lassen sich mehrere Ressourcen nebeneinander betrachten und bearbeitet. Sehr sehr praktisch. Die Dateien einfach speichern und im Visual Studio die externen Änderungen bestätigen.

Das zweite Tool, Resource Refactoring Tool, ist ebenfalls sehr praktisch und erlaubt es Strings, die im Code stehen, schnell in eine Ressourcendatei zu packen. Weiterhin werden dann gleich die Strings durch den Ressourcenaufruf ersetzt.

Conclusion

Das war’s dann auch schon. Ich denke ihr habt eine schicke und effiziente Methode mit der Umsetzung durch .NET gesehen. Sicherlich gibt es noch andere Wege aber dies dürfte eine gute Basis darstellen eigene Anwendungen erfolgreich zu lokalisieren.

Ich möchte noch erwähnen dass das .NET Framework einen sehr großen, hier nicht weiter erörterten Funktionsumfang zur Lokalisierung von Anwendungen bereitstellt. Darunter zählen zum Beispiel Zeitangaben, Satzbau, Formatierungen, weitere Kulturinformationen und vieles mehr. Informationen dazu findet ihr unter anderem in der MSDN Ressourcen und Lokalisierung in ASP.NET 2.0 und NET-Anwendungen erfolgreich lokalisieren, Teil 1.

P
52 Beiträge seit 2015
vor 8 Jahren

Auch wenn diese Anleitung bereits veraltet ist, wäre es möglich, diese so aufzubauen, dass während der Laufzeit die Sprache geändert werden kann, beispielsweise mit menuStrip oder ähnlich damit auch nicht geübte diese anwenden können? Es geht vor allem um Windows Forms.

Der Link zum kompakten Tutorial von Martin H. ist nicht mehr verfügbar.

Gruß Padman
49.485 Beiträge seit 2005
vor 8 Jahren

Hallo Padman,

der Autor des Artikels war 2011 das letzte mal im Forum. Daher halte ich es für unwahrscheinlich, dass der Artikel überarbeitet wird, außer du steuerst selbst die gewünschten Änderungen/Ergänzungen bei.

Den wohl letzten verfügbaren Stand des Tutorials von Martin H. findest du unter folgendem Link (allerdings ohne Bilder und Attachment):

web.archive.org von 2010-01-19 04:41:47: Mehrsprachige Anwendungen in .NET (Lokalisierung)

herbivore

P
52 Beiträge seit 2015
vor 8 Jahren

der Autor des Artikels war 2011 das letzte mal im Forum.

Ist eine Weile her.

Daher halte ich es für unwahrscheinlich, dass der Artikel überarbeitet wird, außer du steuerst selbst die gewünschten Änderungen/Ergänzungen bei.

Dafür reicht mein "Wissen" noch nicht aus.

Den wohl letzten verfügbaren Stand des Tutorials von Martin H. findest du unter folgendem Link (allerdings ohne Bilder und Attachment): [...]

Wurde ja dort archiviert.

Was eventuell im Artikel fehlt wäre, wie man Strings oder ähnliches in der jeweiligen Sprachdatei anfügen kann. Wenn man z.B. die Form1.de-DE.resx per Editor einen Wert einfügen möchte, erscheint eine Meldung, dass der manuell eingefügte Wert wieder gelöscht wird. Von daher wäre auch die Frage, wie man eigene Strings in der jeweiligen Sprachdatei verwenden kann, ohne dass diese gelöscht wird.

Gruß Padman
6.911 Beiträge seit 2009
vor 8 Jahren

Hallo Padman,

z.B. die Form1.de-DE.resx per Editor einen Wert einfügen möchte, erscheint eine Meldung, dass der manuell eingefügte Wert wieder gelöscht wird

Das ist eine Besonderheit der Lokalisierung von Windows-Forms, da hier die Form1.*.resx-Dateien vom Designer generiert werden. Dazu wird während der Lokalisierung auch die Sprache der Form eingestellt und danach die Texte im Designer eingetragen und VS überträgt diese dann in die *.resx-Datei.

Wird jedoch eine eigenständige *.resx-Datei wie oben im Artikel im Abschnitt "Lokalisierung mit .NET" angelegt, so gibt es dieses Überschreib-Problem nicht und neue Texte können einfach hinzugefügt werden.

mfG Gü

Stellt fachliche Fragen bitte im Forum, damit von den Antworten alle profitieren. Daher beantworte ich solche Fragen nicht per PM.

"Alle sagten, das geht nicht! Dann kam einer, der wusste das nicht - und hat's gemacht!"

P
52 Beiträge seit 2015
vor 8 Jahren

Zusammenfassung für die Leute, die ihre eigenen Ressourcen einbinden möchten, ohne in die Ressourcen vom Designer gelöscht werden würden.

Form1 (wird vom Designer verwaltet)*Form1.resx *Form1.de-DE.resx *Form1.fr-FR.resx

Eigene Ressourcen (manuelle Verwaltung):
Properties (Eigenenschaften)*Resources.resx *Resources.de-DE.resx *Resources.fr-FR.resx

Nehmen wir an, die Hauptsprache ist Englisch-US, alle Grundwerte werden in Form1.resx und Resources.resx abgelegt. Sollte man z.B. gerade die Sprache Deutsch (Deutschland) / German (Germany) im Designer bearbeiten und aus versehen z.B. die Größe einer Textbos verändert haben und die Originaleinstellung wieder bekommen, wird einfach die Datei Form1.de-DE.resx geöffnet und wählt Other (andere), alle hier angezeigten Werte betrifft wie hier nur die deutsche Sprachdatei, wenn die Werte gelöscht werden, greifen die Werte aus Resources.resx, also aus der Originaldatei.

Gruß Padman